Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (revision 1046) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java (working copy) @@ -115,6 +115,7 @@ // Type registration messages public static I18n invalidNodeTypeName; + public static I18n noSuchNodeType; public static I18n nodeTypeAlreadyExists; public static I18n invalidPrimaryTypeName; public static I18n invalidSupertypeName; @@ -139,6 +140,10 @@ public static I18n nodeNotReferenceable; public static I18n noPendingChangesAllowed; + public static I18n cannotUnregisterSupertype; + public static I18n cannotUnregisterRequiredPrimaryType; + public static I18n cannotUnregisterDefaultPrimaryType; + static { try { I18n.initialize(JcrI18n.class); Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java (revision 1046) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java (working copy) @@ -94,5 +94,4 @@ public void remove() throws RepositoryException { editorForParent().destroyChild(nodeUuid); } - } Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java (revision 1046) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java (working copy) @@ -165,6 +165,10 @@ return thisAndAllSupertypes; } + List supertypes() { + return allSupertypes; + } + /** * Get the child definitions defined on this node type (excluding inherited definitions). * Index: dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java (revision 1046) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeTypeManager.java (working copy) @@ -39,6 +39,7 @@ import net.jcip.annotations.Immutable; import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.property.Name; +import org.jboss.dna.graph.property.NameFactory; import org.jboss.dna.jcr.nodetype.InvalidNodeTypeDefinitionException; import org.jboss.dna.jcr.nodetype.NodeDefinitionTemplate; import org.jboss.dna.jcr.nodetype.NodeTypeDefinition; @@ -416,6 +417,32 @@ } /** + * Allows the collection of node types to be unregistered if they are not referenced by other node types as supertypes, + * default primary types of child nodes, or required primary types of child nodes. + *

+ * NOTE: This method does not check to see if any of the node types are currently being used. Unregistering a node type + * that is being used will cause the system to become unstable + *

+ * + * @param nodeTypeNames the names of the node types to be unregistered + * @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered node type + * @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot be unregistered because + * they are the supertype, one of the required primary types, or a default primary type of a node type that is not + * being unregistered. + * @throws RepositoryException if any other error occurs + */ + public void unregisterNodeType( Collection nodeTypeNames ) + throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException { + NameFactory nameFactory = this.context.getValueFactories().getNameFactory(); + + Collection names = new ArrayList(nodeTypeNames.size()); + for (String name : nodeTypeNames) { + names.add(nameFactory.create(name)); + } + repositoryTypeManager.unregisterNodeType(names); + } + + /** * Returns an empty {@code NodeTypeTemplate} which can then be used to define a node type and passed to * {@link JcrNodeTypeManager#registerNodeType(NodeTypeDefinition, boolean)} * Index: dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java =================================================================== --- dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java (revision 1046) +++ dna-jcr/src/main/java/org/jboss/dna/jcr/RepositoryNodeTypeManager.java (working copy) @@ -38,6 +38,7 @@ import javax.jcr.UnsupportedRepositoryOperationException; import javax.jcr.Value; import javax.jcr.ValueFormatException; +import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.PropertyDefinition; @@ -46,6 +47,7 @@ import net.jcip.annotations.ThreadSafe; import org.jboss.dna.common.text.TextEncoder; import org.jboss.dna.common.text.XmlNameEncoder; +import org.jboss.dna.common.util.CheckArg; import org.jboss.dna.graph.ExecutionContext; import org.jboss.dna.graph.Graph; import org.jboss.dna.graph.Location; @@ -1100,6 +1102,89 @@ } /** + * Allows the collection of node types to be unregistered if they are not referenced by other node types as supertypes, + * default primary types of child nodes, or required primary types of child nodes. + *

+ * NOTE: This method does not check to see if any of the node types are currently being used. Unregistering a node type + * that is being used will cause the system to become unstable + *

+ * + * @param nodeTypeNames the names of the node types to be unregistered + * @throws NoSuchNodeTypeException if any of the node type names do not correspond to a registered node type + * @throws InvalidNodeTypeDefinitionException if any of the node types with the given names cannot be unregistered because + * they are the supertype, one of the required primary types, or a default primary type of a node type that is not + * being unregistered. + * @throws RepositoryException if any other error occurs + */ + void unregisterNodeType( Collection nodeTypeNames ) + throws NoSuchNodeTypeException, InvalidNodeTypeDefinitionException, RepositoryException { + CheckArg.isNotNull(nodeTypeNames, "nodeTypeNames"); + try { + /* + * Grab an exclusive lock on this data to keep other nodes from being added/saved while the unregistration checks are occurring + */ + nodeTypeManagerLock.writeLock().lock(); + + for (Name nodeTypeName : nodeTypeNames) { + /* + * Check that the type names are valid + */ + if (nodeTypeName == null) { + throw new NoSuchNodeTypeException(JcrI18n.invalidNodeTypeName.text()); + } + String name = nodeTypeName.getString(context.getNamespaceRegistry()); + + if (!this.nodeTypes.containsKey(nodeTypeName)) { + throw new NoSuchNodeTypeException(JcrI18n.noSuchNodeType.text(name)); + } + + /* + * Check that no other node definitions have dependencies on any of the named types + */ + for (JcrNodeType nodeType : nodeTypes.values()) { + // If this node is also being unregistered, don't run checks against it + if (nodeTypeNames.contains(nodeType.getInternalName())) { + continue; + } + + for (JcrNodeType supertype : nodeType.supertypes()) { + if (nodeTypeName.equals(supertype.getInternalName())) { + throw new InvalidNodeTypeDefinitionException( + JcrI18n.cannotUnregisterSupertype.text(name, + supertype.getName())); + } + } + + for (JcrNodeDefinition childNode : nodeType.childNodeDefinitions()) { + NodeType defaultPrimaryType = childNode.getDefaultPrimaryType(); + if (defaultPrimaryType != null && name.equals(defaultPrimaryType.getName())) { + throw new InvalidNodeTypeDefinitionException( + JcrI18n.cannotUnregisterDefaultPrimaryType.text(name, + nodeType.getName(), + childNode.getName())); + } + if (childNode.getRequiredPrimaryTypeNames().contains(nodeTypeName)) { + throw new InvalidNodeTypeDefinitionException( + JcrI18n.cannotUnregisterRequiredPrimaryType.text(name, + nodeType.getName(), + childNode.getName())); + } + } + } + } + + /* + * Do a full recursive search over the content graph to make sure that this type isn't being used + */ + // TODO: replace this with a query after queries work + this.nodeTypes.keySet().removeAll(nodeTypeNames); + + } finally { + nodeTypeManagerLock.writeLock().unlock(); + } + } + + /** * Registers a new node type or updates an existing node type using the specified definition and returns the resulting {@code * NodeType} object. *

@@ -1803,15 +1888,13 @@ for (JcrNodeDefinition ancestor : ancestors) { if (ancestor.isProtected()) { throw new InvalidNodeTypeDefinitionException( - JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType() - .getName(), + JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType().getName(), "child node")); } if (ancestor.isMandatory() && !node.isMandatory()) { throw new InvalidNodeTypeDefinitionException( - JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType() - .getName(), + JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType().getName(), "child node")); } @@ -1876,16 +1959,15 @@ Value[] defaultValues = prop.getDefaultValues(); if (prop.isAutoCreated() && !prop.isProtected() && (defaultValues == null || defaultValues.length == 0)) { - throw new InvalidNodeTypeDefinitionException(JcrI18n.autocreatedPropertyNeedsDefault.text(prop.getName(), - prop.getDeclaringNodeType() - .getName())); + throw new InvalidNodeTypeDefinitionException( + JcrI18n.autocreatedPropertyNeedsDefault.text(prop.getName(), + prop.getDeclaringNodeType().getName())); } if (!prop.isMultiple() && (defaultValues != null && defaultValues.length > 1)) { throw new InvalidNodeTypeDefinitionException( JcrI18n.singleValuedPropertyNeedsSingleValuedDefault.text(prop.getName(), - prop.getDeclaringNodeType() - .getName())); + prop.getDeclaringNodeType().getName())); } Name propName = context.getValueFactories().getNameFactory().create(prop.getName()); @@ -1899,15 +1981,13 @@ for (JcrPropertyDefinition ancestor : ancestors) { if (ancestor.isProtected()) { throw new InvalidNodeTypeDefinitionException( - JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType() - .getName(), + JcrI18n.cannotOverrideProtectedDefinition.text(ancestor.getDeclaringNodeType().getName(), "property")); } if (ancestor.isMandatory() && !prop.isMandatory()) { throw new InvalidNodeTypeDefinitionException( - JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType() - .getName(), + JcrI18n.cannotMakeMandatoryDefinitionOptional.text(ancestor.getDeclaringNodeType().getName(), "property")); } @@ -1918,16 +1998,14 @@ && !Arrays.equals(ancestor.getValueConstraints(), prop.getValueConstraints())) { throw new InvalidNodeTypeDefinitionException( JcrI18n.constraintsChangedInSubtype.text(propName, - ancestor.getDeclaringNodeType() - .getName())); + ancestor.getDeclaringNodeType().getName())); } if (!isAlwaysSafeConversion(prop.getRequiredType(), ancestor.getRequiredType())) { throw new InvalidNodeTypeDefinitionException( JcrI18n.cannotRedefineProperty.text(propName, PropertyType.nameFromValue(prop.getRequiredType()), - ancestor.getDeclaringNodeType() - .getName(), + ancestor.getDeclaringNodeType().getName(), PropertyType.nameFromValue(ancestor.getRequiredType()))); } Index: dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties =================================================================== --- dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (revision 1046) +++ dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties (working copy) @@ -100,6 +100,7 @@ invalidQueryLanguage="{0}" is not a valid query langauge. Supported languages are\: {1} invalidNodeTypeName=Node types cannot have a null or empty name +noSuchNodeType=Type named '{0}' does not exist nodeTypeAlreadyExists=Node type '{0}' already exists invalidPrimaryTypeName=Required primary type '{0}' in type '{1}' does not exist invalidSupertypeName=Supertype '{0}' from type '{1}' does not exist @@ -123,3 +124,7 @@ nodeNotReferenceable=Only referenceable nodes may be the value of reference properties noPendingChangesAllowed=This operation cannot be performed when the session has pending changes + +cannotUnregisterSupertype=Cannot unregister type '{0}' which is supertype of type '{1}' +cannotUnregisterRequiredPrimaryType=Cannot unregister type '{0}' which is the required primary type for child node '{2}' on type '{1}' +cannotUnregisterDefaultPrimaryType=Cannot unregister type '{0}' which is the default primary type for child node '{2}' of type '{1}' \ No newline at end of file Index: dna-jcr/src/test/java/org/jboss/dna/jcr/TypeRegistrationTest.java =================================================================== --- dna-jcr/src/test/java/org/jboss/dna/jcr/TypeRegistrationTest.java (revision 1046) +++ dna-jcr/src/test/java/org/jboss/dna/jcr/TypeRegistrationTest.java (working copy) @@ -25,12 +25,15 @@ import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; +import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import java.io.IOException; import java.util.Arrays; import java.util.List; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.nodetype.NodeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.jboss.dna.graph.ExecutionContext; @@ -41,6 +44,7 @@ import org.jboss.dna.jcr.nodetype.NodeDefinitionTemplate; import org.jboss.dna.jcr.nodetype.NodeTypeDefinition; import org.jboss.dna.jcr.nodetype.NodeTypeExistsException; +import org.jboss.dna.jcr.nodetype.NodeTypeTemplate; import org.jboss.dna.jcr.nodetype.PropertyDefinitionTemplate; import org.junit.Before; import org.junit.Test; @@ -703,6 +707,103 @@ compareTemplatesToNodeTypes(templates, repoTypeManager.registerNodeTypes(templates, false)); } + /* + * Unregistration tests + */ + + @Test (expected=IllegalArgumentException.class) + public void shouldNotAllowUnregisteringNullCollection() throws Exception { + repoTypeManager.unregisterNodeType(null); + } + + @Test (expected=NoSuchNodeTypeException.class) + public void shouldNotAllowUnregisteringInvalidTypeNames() throws Exception { + repoTypeManager.unregisterNodeType(Arrays.asList(new Name[] { JcrNtLexicon.FILE, JcrLexicon.DATA})); + } + + @Test (expected=InvalidNodeTypeDefinitionException.class) + public void shouldNotAllowUnregisteringSupertype() throws Exception { + repoTypeManager.unregisterNodeType(Arrays.asList(new Name[] { JcrNtLexicon.HIERARCHY_NODE, })); + + } + + @Test (expected=InvalidNodeTypeDefinitionException.class) + public void shouldNotAllowUnregisteringRequiredPrimaryType() throws Exception { + repoTypeManager.unregisterNodeType(Arrays.asList(new Name[] { JcrNtLexicon.FROZEN_NODE, })); + } + + @Test (expected=InvalidNodeTypeDefinitionException.class) + public void shouldNotAllowUnregisteringDefaultPrimaryType() throws Exception { + ntTemplate.setName(TEST_TYPE_NAME); + + JcrNodeDefinitionTemplate childNode = new JcrNodeDefinitionTemplate(this.context); + childNode.setDefaultPrimaryType(JcrNtLexicon.FILE.getString(this.registry)); + ntTemplate.getNodeDefinitionTemplates().add(childNode); + + try { + repoTypeManager.registerNodeType(ntTemplate, false); + } + catch (Exception ex) { + fail(ex.getMessage()); + } + + repoTypeManager.unregisterNodeType(Arrays.asList(new Name[] { JcrNtLexicon.FILE, })); + } + + @Test + public void shouldAllowUnregisteringUnusedType() throws Exception { + ntTemplate.setName(TEST_TYPE_NAME); + + JcrNodeDefinitionTemplate childNode = new JcrNodeDefinitionTemplate(this.context); + childNode.setDefaultPrimaryType(JcrNtLexicon.FILE.getString(this.registry)); + ntTemplate.getNodeDefinitionTemplates().add(childNode); + + try { + repoTypeManager.registerNodeType(ntTemplate, false); + } + catch (Exception ex) { + fail(ex.getMessage()); + } + + Name typeNameAsName = nameFactory.create(TEST_TYPE_NAME); + int nodeTypeCount = repoTypeManager.getAllNodeTypes().size(); + repoTypeManager.unregisterNodeType(Arrays.asList(new Name[] { typeNameAsName })); + assertThat(repoTypeManager.getAllNodeTypes().size(), is(nodeTypeCount - 1)); + assertThat(repoTypeManager.getNodeType(typeNameAsName), is(nullValue())); + } + + @Test + public void shouldAllowUnregisteringUnusedTypesWithMutualDependencies() throws Exception { + ntTemplate.setName(TEST_TYPE_NAME); + + JcrNodeDefinitionTemplate childNode = new JcrNodeDefinitionTemplate(this.context); + childNode.setDefaultPrimaryType(TEST_TYPE_NAME2); + ntTemplate.getNodeDefinitionTemplates().add(childNode); + + NodeTypeTemplate ntTemplate2 = new JcrNodeTypeTemplate(this.context); + ntTemplate2.setName(TEST_TYPE_NAME2); + + JcrNodeDefinitionTemplate childNode2 = new JcrNodeDefinitionTemplate(this.context); + childNode2.setDefaultPrimaryType(TEST_TYPE_NAME); + ntTemplate2.getNodeDefinitionTemplates().add(childNode2); + + try { + repoTypeManager.registerNodeTypes(Arrays.asList(new NodeTypeDefinition[] { ntTemplate, ntTemplate2 }), false); + } + catch (Exception ex) { + fail(ex.getMessage()); + } + + Name typeNameAsName = nameFactory.create(TEST_TYPE_NAME); + Name type2NameAsName = nameFactory.create(TEST_TYPE_NAME2); + int nodeTypeCount = repoTypeManager.getAllNodeTypes().size(); + repoTypeManager.unregisterNodeType(Arrays.asList(new Name[] { typeNameAsName, type2NameAsName })); + assertThat(repoTypeManager.getAllNodeTypes().size(), is(nodeTypeCount - 2)); + assertThat(repoTypeManager.getNodeType(typeNameAsName), is(nullValue())); + assertThat(repoTypeManager.getNodeType(type2NameAsName), is(nullValue())); + + } + private void compareTemplatesToNodeTypes( List templates, List nodeTypes ) { assertThat(templates.size(), is(nodeTypes.size())); @@ -826,4 +927,5 @@ // assertThat(template.getRequiredPrimaryTypeNames(), is(definition.getRequiredPrimaryTypeNames())); } + }