From 7c774e1356257bd64273760740f2464f2d6661fb Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Tue, 09 Jan 2007 20:20:30 +0000
Subject: [PATCH] Update the schema backend to provide full support for online schema updates. which includes the following:

---
 opends/src/server/org/opends/server/types/DITContentRule.java                                    |  710 +--
 opends/src/server/org/opends/server/schema/DITContentRuleSyntax.java                             |   41 
 opends/src/server/org/opends/server/types/DITStructureRule.java                                  |  485 +-
 opends/src/server/org/opends/server/types/CommonSchemaElements.java                              |   99 
 opends/src/server/org/opends/server/schema/MatchingRuleUseSyntax.java                            |   29 
 opends/src/server/org/opends/server/schema/ObjectClassSyntax.java                                |    9 
 opends/src/server/org/opends/server/messages/CoreMessages.java                                   |   17 
 opends/src/server/org/opends/server/util/ServerConstants.java                                    |   51 
 opends/src/server/org/opends/server/types/AttributeType.java                                     |   87 
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestAttributeType.java         |  167 
 opends/src/server/org/opends/server/core/SchemaConfigManager.java                                |    5 
 opends/src/server/org/opends/server/plugins/EntryUUIDPlugin.java                                 |   14 
 opends/src/server/org/opends/server/backends/SchemaBackend.java                                  | 3003 +++++++++++++++-
 opends/src/server/org/opends/server/schema/DITStructureRuleSyntax.java                           |   78 
 opends/src/server/org/opends/server/types/MatchingRuleUse.java                                   |  452 +-
 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaTestMatchingRule.java |  190 +
 opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestObjectClass.java           |  168 
 opends/src/server/org/opends/server/types/SchemaFileElement.java                                 |  107 
 opends/src/server/org/opends/server/types/NameForm.java                                          |  553 +-
 opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java  | 3041 +++++++++++++++++
 opends/src/server/org/opends/server/schema/NameFormSyntax.java                                   |   34 
 opends/src/server/org/opends/server/core/DirectoryServer.java                                    |   42 
 opends/src/server/org/opends/server/types/Schema.java                                            |  264 +
 opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java                              |   14 
 opends/src/server/org/opends/server/types/ObjectClass.java                                       |  133 
 opends/src/server/org/opends/server/messages/BackendMessages.java                                |  800 ++++
 opends/resource/schema/00-core.ldif                                                              |   28 
 27 files changed, 8,864 insertions(+), 1,757 deletions(-)

diff --git a/opends/resource/schema/00-core.ldif b/opends/resource/schema/00-core.ldif
index b1e41d2..06db180 100644
--- a/opends/resource/schema/00-core.ldif
+++ b/opends/resource/schema/00-core.ldif
@@ -21,7 +21,7 @@
 # CDDL HEADER END
 #
 #
-#      Portions Copyright 2006 Sun Microsystems, Inc.
+#      Portions Copyright 2006-2007 Sun Microsystems, Inc.
 #
 #
 # This file contains a core set of attribute type and objectlass definitions
@@ -172,20 +172,16 @@
   EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
   SINGLE-VALUE NO-USER-MODIFICATION USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
-attributeTypes: ( 2.5.21.5 NAME 'attributeTypes'
-  EQUALITY objectIdentifierFirstComponentMatch
+attributeTypes: ( 2.5.21.5 NAME 'attributeTypes' EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.3 USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
-attributeTypes: ( 2.5.21.6 NAME 'objectClasses'
-  EQUALITY objectIdentifierFirstComponentMatch
+attributeTypes: ( 2.5.21.6 NAME 'objectClasses' EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.37 USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
-attributeTypes: ( 2.5.21.4 NAME 'matchingRules'
-  EQUALITY objectIdentifierFirstComponentMatch
+attributeTypes: ( 2.5.21.4 NAME 'matchingRules' EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.30 USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
-attributeTypes: ( 2.5.21.8 NAME 'matchingRuleUse'
-  EQUALITY objectIdentifierFirstComponentMatch
+attributeTypes: ( 2.5.21.8 NAME 'matchingRuleUse' EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.31 USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
 attributeTypes: ( 1.3.6.1.4.1.1466.101.120.5 NAME 'namingContexts'
@@ -206,15 +202,13 @@
 attributeTypes: ( 1.3.6.1.4.1.1466.101.120.16 NAME 'ldapSyntaxes'
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.54 USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
-attributeTypes: ( 2.5.21.1 NAME 'dITStructureRules'
-  EQUALITY integerFirstComponentMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.17
-  USAGE directoryOperation X-ORIGIN 'RFC 2252' )
-attributeTypes: ( 2.5.21.7 NAME 'nameForms'
-  EQUALITY objectIdentifierFirstComponentMatch
+attributeTypes: ( 2.5.21.1 NAME 'dITStructureRules' EQUALITY caseIgnoreMatch
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.17 USAGE directoryOperation
+  X-ORIGIN 'RFC 2252' )
+attributeTypes: ( 2.5.21.7 NAME 'nameForms' EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.35 USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
-attributeTypes: ( 2.5.21.2 NAME 'dITContentRules'
-  EQUALITY objectIdentifierFirstComponentMatch
+attributeTypes: ( 2.5.21.2 NAME 'dITContentRules' EQUALITY caseIgnoreMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.16 USAGE directoryOperation
   X-ORIGIN 'RFC 2252' )
 attributeTypes: ( 0.9.2342.19200300.100.1.25 NAME 'dc'
@@ -464,7 +458,7 @@
   AUXILIARY X-ORIGIN 'RFC 2252' )
 objectClasses: ( 2.5.20.1 NAME 'subschema' AUXILIARY MAY ( dITStructureRules $
   nameForms $ ditContentRules $ objectClasses $ attributeTypes $ matchingRules $
-  matchingRuleUse ) X-ORIGIn 'RFC 2252' )
+  matchingRuleUse ) X-ORIGIN 'RFC 2252' )
 objectClasses: ( 0.9.2342.19200300.100.4.5 NAME 'account' SUP top STRUCTURAL
   MUST uid MAY ( description $ seeAlso $ l $ o $ ou $ host )
   X-ORIGIN 'RFC 4524' )
diff --git a/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opends/src/server/org/opends/server/backends/SchemaBackend.java
index 59956c5..6b5d099 100644
--- a/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.backends;
 
@@ -32,6 +32,7 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.InputStream;
+import java.io.IOException;
 import java.io.OutputStream;
 import java.security.MessageDigest;
 import java.util.ArrayList;
@@ -43,6 +44,8 @@
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.zip.Deflater;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
@@ -52,8 +55,10 @@
 import javax.crypto.CipherOutputStream;
 import javax.crypto.Mac;
 
+import org.opends.server.api.AlertGenerator;
 import org.opends.server.api.Backend;
 import org.opends.server.api.ConfigurableComponent;
+import org.opends.server.api.MatchingRule;
 import org.opends.server.config.BooleanConfigAttribute;
 import org.opends.server.config.ConfigAttribute;
 import org.opends.server.config.ConfigEntry;
@@ -67,6 +72,10 @@
 import org.opends.server.core.SchemaConfigManager;
 import org.opends.server.core.SearchOperation;
 import org.opends.server.schema.AttributeTypeSyntax;
+import org.opends.server.schema.DITContentRuleSyntax;
+import org.opends.server.schema.DITStructureRuleSyntax;
+import org.opends.server.schema.MatchingRuleUseSyntax;
+import org.opends.server.schema.NameFormSyntax;
 import org.opends.server.schema.ObjectClassSyntax;
 import org.opends.server.types.Attribute;
 import org.opends.server.types.AttributeType;
@@ -77,6 +86,8 @@
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.CryptoManager;
 import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DITContentRule;
+import org.opends.server.types.DITStructureRule;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
 import org.opends.server.types.ErrorLogCategory;
@@ -85,16 +96,21 @@
 import org.opends.server.types.InitializationException;
 import org.opends.server.types.LDIFImportConfig;
 import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.MatchingRuleUse;
 import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.NameForm;
 import org.opends.server.types.ObjectClass;
+import org.opends.server.types.ObjectClassType;
 import org.opends.server.types.RDN;
 import org.opends.server.types.RestoreConfig;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.Schema;
+import org.opends.server.types.SchemaFileElement;
 import org.opends.server.types.SearchFilter;
 import org.opends.server.types.SearchScope;
 import org.opends.server.util.DynamicConstants;
-import org.opends.server.util.LDIFReader;
+import org.opends.server.util.LDIFException;
 import org.opends.server.util.LDIFWriter;
 
 import static org.opends.server.config.ConfigConstants.*;
@@ -114,7 +130,7 @@
  */
 public class SchemaBackend
        extends Backend
-       implements ConfigurableComponent
+       implements ConfigurableComponent, AlertGenerator
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -844,7 +860,8 @@
     // elements, nor will we allow modification of any other attributes.  Make
     // sure that the included modify operation is acceptable within these
     // constraints.
-    List<Modification> mods = modifyOperation.getModifications();
+    ArrayList<Modification> mods =
+         new ArrayList<Modification>(modifyOperation.getModifications());
     if (mods.isEmpty())
     {
       // There aren't any modifications, so we don't need to do anything.
@@ -852,11 +869,15 @@
     }
 
     Schema newSchema = DirectoryServer.getSchema().duplicate();
-    LinkedList<AttributeType> newAttrTypes = new LinkedList<AttributeType>();
-    LinkedList<ObjectClass> newObjectClasses = new LinkedList<ObjectClass>();
+    TreeSet<String> modifiedSchemaFiles = new TreeSet<String>();
+    LinkedHashSet<SchemaFileElement> dependentElements =
+         new LinkedHashSet<SchemaFileElement>();
 
+    int pos = -1;
     for (Modification m : mods)
     {
+      pos++;
+
       if (m.isInternal())
       {
         // We don't need to do anything for internal modifications (e.g., like
@@ -864,301 +885,2026 @@
         continue;
       }
 
+
+      // Determine the type of modification to perform.  We will support add and
+      // delete operations in the schema, and we will also support the ability
+      // to add a schema element that already exists and treat it as a
+      // replacement of that existing element.
+      Attribute     a  = m.getAttribute();
+      AttributeType at = a.getAttributeType();
       switch (m.getModificationType())
       {
         case ADD:
-          // This is fine, as long as there aren't any conflicts later.
+          LinkedHashSet<AttributeValue> values = a.getValues();
+          if (values.isEmpty())
+          {
+            continue;
+          }
+
+          if (at.equals(attributeTypesType))
+          {
+            for (AttributeValue v : values)
+            {
+              AttributeType type;
+              try
+              {
+                type = AttributeTypeSyntax.decodeAttributeType(v.getValue(),
+                                                               newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_ATTRTYPE;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              addAttributeType(type, newSchema, modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(objectClassesType))
+          {
+            for (AttributeValue v : values)
+            {
+              ObjectClass oc;
+              try
+              {
+                oc = ObjectClassSyntax.decodeObjectClass(v.getValue(),
+                                                         newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              addObjectClass(oc, newSchema, modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(nameFormsType))
+          {
+            for (AttributeValue v : values)
+            {
+              NameForm nf;
+              try
+              {
+                nf = NameFormSyntax.decodeNameForm(v.getValue(), newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              addNameForm(nf, newSchema, modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(ditContentRulesType))
+          {
+            for (AttributeValue v : values)
+            {
+              DITContentRule dcr;
+              try
+              {
+                dcr = DITContentRuleSyntax.decodeDITContentRule(v.getValue(),
+                                                                newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DCR;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              addDITContentRule(dcr, newSchema, modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(ditStructureRulesType))
+          {
+            for (AttributeValue v : values)
+            {
+              DITStructureRule dsr;
+              try
+              {
+                dsr = DITStructureRuleSyntax.decodeDITStructureRule(
+                           v.getValue(), newSchema, false);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DSR;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              addDITStructureRule(dsr, newSchema, modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(matchingRuleUsesType))
+          {
+            for (AttributeValue v : values)
+            {
+              MatchingRuleUse mru;
+              try
+              {
+                mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(v.getValue(),
+                                                                  newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              addMatchingRuleUse(mru, newSchema, modifiedSchemaFiles);
+            }
+          }
+          else
+          {
+            int    msgID   = MSGID_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE;
+            String message = getMessage(msgID, a.getName());
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message, msgID);
+          }
+
           break;
 
+
         case DELETE:
-          // FIXME -- We need to support this.
-          int    msgID   = MSGID_SCHEMA_DELETE_MODTYPE_NOT_SUPPORTED;
-          String message = getMessage(msgID);
-          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
-                                       msgID);
+          values = a.getValues();
+          if (values.isEmpty())
+          {
+            int    msgID   = MSGID_SCHEMA_MODIFY_DELETE_NO_VALUES;
+            String message = getMessage(msgID, a.getName());
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message, msgID);
+          }
 
-        case REPLACE:
-          // FIXME -- Should we support this?
-        case INCREMENT:
+          if (at.equals(attributeTypesType))
+          {
+            for (AttributeValue v : values)
+            {
+              AttributeType type;
+              try
+              {
+                type = AttributeTypeSyntax.decodeAttributeType(v.getValue(),
+                                                               newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_ATTRTYPE;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              removeAttributeType(type, newSchema, mods, pos,
+                                  modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(objectClassesType))
+          {
+            for (AttributeValue v : values)
+            {
+              ObjectClass oc;
+              try
+              {
+                oc = ObjectClassSyntax.decodeObjectClass(v.getValue(),
+                                                         newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              removeObjectClass(oc, newSchema, mods, pos, modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(nameFormsType))
+          {
+            for (AttributeValue v : values)
+            {
+              NameForm nf;
+              try
+              {
+                nf = NameFormSyntax.decodeNameForm(v.getValue(), newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              removeNameForm(nf, newSchema, mods, pos, modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(ditContentRulesType))
+          {
+            for (AttributeValue v : values)
+            {
+              DITContentRule dcr;
+              try
+              {
+                dcr = DITContentRuleSyntax.decodeDITContentRule(v.getValue(),
+                                                                newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DCR;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              removeDITContentRule(dcr, newSchema, mods, pos,
+                                   modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(ditStructureRulesType))
+          {
+            for (AttributeValue v : values)
+            {
+              DITStructureRule dsr;
+              try
+              {
+                dsr = DITStructureRuleSyntax.decodeDITStructureRule(
+                           v.getValue(), newSchema, false);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DSR;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              removeDITStructureRule(dsr, newSchema, mods, pos,
+                                     modifiedSchemaFiles);
+            }
+          }
+          else if (at.equals(matchingRuleUsesType))
+          {
+            for (AttributeValue v : values)
+            {
+              MatchingRuleUse mru;
+              try
+              {
+                mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(v.getValue(),
+                                                                  newSchema);
+              }
+              catch (DirectoryException de)
+              {
+                assert debugException(CLASS_NAME, "replaceEntry", de);
+
+                int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE;
+                String message = getMessage(msgID, v.getStringValue(),
+                                            de.getErrorMessage());
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID, de);
+              }
+
+              removeMatchingRuleUse(mru, newSchema, mods, pos,
+                                    modifiedSchemaFiles);
+            }
+          }
+          else
+          {
+            int    msgID   = MSGID_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE;
+            String message = getMessage(msgID, a.getName());
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message, msgID);
+          }
+
+          break;
+
+
         default:
-          // FIXME -- Make sure to update this message once we support
-          // schema deletes and possibly replace.
-          msgID   = MSGID_SCHEMA_INVALID_MODIFICATION_TYPE;
-          message = getMessage(msgID, m.getModificationType());
+          int    msgID   = MSGID_SCHEMA_INVALID_MODIFICATION_TYPE;
+          String message = getMessage(msgID, m.getModificationType());
           throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
                                        msgID);
       }
-
-
-      // At the present time, we will only allow modification of the
-      // attributeTypes and objectClasses attributes.
-      Attribute     a = m.getAttribute();
-      AttributeType t = a.getAttributeType();
-      if (t.equals(attributeTypesType))
-      {
-        for (AttributeValue v : a.getValues())
-        {
-          AttributeType newType;
-          try
-          {
-            newType = AttributeTypeSyntax.decodeAttributeType(v.getValue(),
-                                                              newSchema);
-          }
-          catch (DirectoryException de)
-          {
-            assert debugException(CLASS_NAME, "replaceEntry", de);
-
-            int    msgID   = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_ATTRTYPE;
-            String message = getMessage(msgID, v.getStringValue(),
-                                        de.getErrorMessage());
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID, de);
-          }
-
-          try
-          {
-            newSchema.registerAttributeType(newType, false);
-            newAttrTypes.add(newType);
-          }
-          catch (DirectoryException de)
-          {
-            assert debugException(CLASS_NAME, "replaceEntry", de);
-
-            int    msgID   = MSGID_SCHEMA_MODIFY_ATTRTYPE_ALREADY_EXISTS;
-            String message = getMessage(msgID, newType.getNameOrOID(),
-                                        de.getErrorMessage());
-            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                                         message, msgID, de);
-          }
-        }
-      }
-      else if (t.equals(objectClassesType))
-      {
-        for (AttributeValue v : a.getValues())
-        {
-          ObjectClass newClass;
-          try
-          {
-            newClass = ObjectClassSyntax.decodeObjectClass(v.getValue(),
-                                                           newSchema);
-          }
-          catch (DirectoryException de)
-          {
-            assert debugException(CLASS_NAME, "replaceEntry", de);
-
-            int    msgID   = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS;
-            String message = getMessage(msgID, v.getStringValue(),
-                                        de.getErrorMessage());
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID, de);
-          }
-
-          // If there is a superior class, then make sure it is defined.
-          ObjectClass superiorClass = newClass.getSuperiorClass();
-          if (superiorClass != null)
-          {
-            String lowerName = toLowerCase(superiorClass.getNameOrOID());
-            if (! newSchema.hasObjectClass(lowerName))
-            {
-              int msgID = MSGID_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_OBJECTCLASS;
-              String message = getMessage(msgID, newClass.getNameOrOID(),
-                                          superiorClass.getNameOrOID());
-              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                                           message, msgID);
-            }
-          }
-
-          // Make sure that all the associated attribute types are defined.
-          for (AttributeType at : newClass.getRequiredAttributes())
-          {
-            String lowerName = toLowerCase(at.getNameOrOID());
-            if (! newSchema.hasAttributeType(lowerName))
-            {
-              int    msgID   = MSGID_SCHEMA_MODIFY_OC_UNDEFINED_REQUIRED_ATTR;
-              String message = getMessage(msgID, newClass.getNameOrOID(),
-                                          at.getNameOrOID());
-              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                                           message, msgID);
-            }
-          }
-
-          for (AttributeType at : newClass.getOptionalAttributes())
-          {
-            String lowerName = toLowerCase(at.getNameOrOID());
-            if (! newSchema.hasAttributeType(lowerName))
-            {
-              int    msgID   = MSGID_SCHEMA_MODIFY_OC_UNDEFINED_OPTIONAL_ATTR;
-              String message = getMessage(msgID, newClass.getNameOrOID(),
-                                          at.getNameOrOID());
-              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                                           message, msgID);
-            }
-          }
-
-          try
-          {
-            newSchema.registerObjectClass(newClass, false);
-            newObjectClasses.add(newClass);
-          }
-          catch (DirectoryException de)
-          {
-            assert debugException(CLASS_NAME, "replaceEntry", de);
-
-            int    msgID   = MSGID_SCHEMA_MODIFY_OBJECTCLASS_ALREADY_EXISTS;
-            String message = getMessage(msgID, newClass.getNameOrOID(),
-                                        de.getErrorMessage());
-            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
-                                         message, msgID, de);
-          }
-        }
-      }
-      else
-      {
-        int    msgID   = MSGID_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE;
-        String message = getMessage(msgID, a.getName());
-        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
-                                     msgID);
-      }
     }
 
 
-    // If we've gotten here, then everything looks OK.  Add the new schema
-    // elements to the 99-user.ldif file and swing the new schema into place.
-    String schemaDirPath = SchemaConfigManager.getSchemaDirectoryPath();
-    File userSchemaFile = new File(schemaDirPath, FILE_USER_SCHEMA_ELEMENTS);
-    Entry userSchemaEntry = null;
-
-    if (userSchemaFile.exists())
-    {
-      // There's already a set of user-defined schema elements, so we'll need to
-      // add these new elements to that set.
-      LDIFReader ldifReader = null;
-      try
-      {
-        LDIFImportConfig importConfig =
-             new LDIFImportConfig(userSchemaFile.getAbsolutePath());
-        ldifReader = new LDIFReader(importConfig);
-
-        userSchemaEntry = ldifReader.readEntry(true);
-      }
-      catch (Exception e)
-      {
-        assert debugException(CLASS_NAME, "replaceEntry", e);
-
-        int    msgID   = MSGID_SCHEMA_MODIFY_CANNOT_READ_EXISTING_USER_SCHEMA;
-        String message = getMessage(msgID, userSchemaFile.getAbsolutePath(),
-                                    stackTraceToSingleLineString(e));
-        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
-                                     message, msgID, e);
-      }
-      finally
-      {
-        if (ldifReader != null)
-        {
-          ldifReader.close();
-        }
-      }
-    }
-
-    if (userSchemaEntry == null)
-    {
-      // This could happen if there was no user schema file or if it was there
-      // but didn't have any entries.  At any rate, create a new, empty entry.
-      userSchemaEntry = createEmptySchemaEntry();
-    }
-
-
-    // Add all of the new schema elements to the entry.
-    if (! newAttrTypes.isEmpty())
-    {
-      LinkedHashSet<AttributeValue> values =
-           new LinkedHashSet<AttributeValue>();
-      for (AttributeType t : newAttrTypes)
-      {
-        StringBuilder buffer = new StringBuilder();
-        t.toString(buffer, false);
-        values.add(new AttributeValue(attributeTypesType, buffer.toString()));
-      }
-
-      Attribute attrTypeAttribute = new Attribute(attributeTypesType,
-                                                  ATTR_ATTRIBUTE_TYPES, values);
-      LinkedList<AttributeValue> duplicateValues =
-           new LinkedList<AttributeValue>();
-      userSchemaEntry.addAttribute(attrTypeAttribute, duplicateValues);
-    }
-
-    if (! newObjectClasses.isEmpty())
-    {
-      LinkedHashSet<AttributeValue> values =
-           new LinkedHashSet<AttributeValue>();
-      for (ObjectClass oc : newObjectClasses)
-      {
-        StringBuilder buffer = new StringBuilder();
-        oc.toString(buffer, false);
-        values.add(new AttributeValue(attributeTypesType, buffer.toString()));
-      }
-
-      Attribute ocAttribute = new Attribute(objectClassesType,
-                                            ATTR_OBJECTCLASSES, values);
-      LinkedList<AttributeValue> duplicateValues =
-           new LinkedList<AttributeValue>();
-      userSchemaEntry.addAttribute(ocAttribute, duplicateValues);
-    }
-
-
-    // Swing the new schema into place.
+    // If we've gotten here, then everything looks OK.  We'll re-write all
+    // impacted schema files by first creating them in a temporary location
+    // and then replacing the existing schema files with the new versions.
+    // If all that goes successfully, then activate the new schema.
+    HashMap<String,File> tempSchemaFiles = new HashMap<String,File>();
     try
     {
-      File tempSchemaFile = new File(userSchemaFile.getAbsolutePath() + ".tmp");
-      LDIFExportConfig exportConfig =
-           new LDIFExportConfig(tempSchemaFile.getAbsolutePath(),
-                                ExistingFileBehavior.OVERWRITE);
-
-      LDIFWriter writer = null;
-      try
+      for (String schemaFile : modifiedSchemaFiles)
       {
-        writer = new LDIFWriter(exportConfig);
-        writer.writeEntry(userSchemaEntry);
-      }
-      finally
-      {
-        if (writer != null)
-        {
-          writer.close();
-        }
+        File tempSchemaFile = writeTempSchemaFile(newSchema, schemaFile);
+        tempSchemaFiles.put(schemaFile, tempSchemaFile);
       }
 
-      File oldSchemaFile = null;
-      if (userSchemaFile.exists())
-      {
-        oldSchemaFile = new File(userSchemaFile.getAbsolutePath() + ".old");
-        if (oldSchemaFile.exists())
-        {
-          oldSchemaFile.delete();
-        }
-
-        userSchemaFile.renameTo(oldSchemaFile);
-      }
-
-      tempSchemaFile.renameTo(userSchemaFile);
-
-      if (oldSchemaFile != null)
-      {
-        oldSchemaFile.delete();
-      }
-
+      installSchemaFiles(tempSchemaFiles);
       DirectoryServer.setSchema(newSchema);
     }
+    catch (DirectoryException de)
+    {
+      assert debugException(CLASS_NAME, "replaceEntry", de);
+
+      throw de;
+    }
     catch (Exception e)
     {
       assert debugException(CLASS_NAME, "replaceEntry", e);
 
       int    msgID   = MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_SCHEMA;
-      String message = getMessage(msgID, userSchemaFile.getAbsolutePath(),
-                                  stackTraceToSingleLineString(e));
+      String message = getMessage(msgID, stackTraceToSingleLineString(e));
       throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
                                    message, msgID, e);
     }
+    finally
+    {
+      cleanUpTempSchemaFiles(tempSchemaFiles);
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required for adding the provided attribute type to
+   * the given schema, replacing an existing type if necessary, and ensuring all
+   * other metadata is properly updated.
+   *
+   * @param  attributeType        The attribute type to add or replace in the
+   *                              server schema.
+   * @param  schema               The schema to which the attribute type should
+   *                              be added.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to add
+   *                              the provided attribute type to the server
+   *                              schema.
+   */
+  private void addAttributeType(AttributeType attributeType, Schema schema,
+                                Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addAttributeType",
+                      String.valueOf(attributeType), String.valueOf(schema),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // First, see if the specified attribute type already exists.  We'll check
+    // the OID and all of the names, which means that it's possible there could
+    // be more than one match (although if there is, then we'll refuse the
+    // operation).
+    AttributeType existingType =
+         schema.getAttributeType(attributeType.getOID());
+    for (String name : attributeType.getNormalizedNames())
+    {
+      AttributeType t = schema.getAttributeType(name);
+      if (t == null)
+      {
+        continue;
+      }
+      else if (existingType == null)
+      {
+        existingType = t;
+      }
+      else if (existingType != t)
+      {
+        // NOTE:  We really do want to use "!=" instead of "! t.equals()"
+        // because we want to check whether it's the same object instance, not
+        // just a logical equivalent.
+        int msgID = MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_ATTRTYPE;
+        String message = getMessage(msgID, attributeType.getNameOrOID(),
+                                    existingType.getNameOrOID(),
+                                    t.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the new attribute type doesn't reference an undefined
+    // superior attribute type.
+    AttributeType superiorType = attributeType.getSuperiorType();
+    if (superiorType != null)
+    {
+      if (! schema.hasAttributeType(superiorType.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_ATTRIBUTE_TYPE;
+        String message = getMessage(msgID, attributeType.getNameOrOID(),
+                                    superiorType.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If there is no existing type, then we're adding a new attribute.
+    // Otherwise, we're replacing an existing one.
+    if (existingType == null)
+    {
+      schema.registerAttributeType(attributeType, false);
+      String schemaFile = attributeType.getSchemaFile();
+      if ((schemaFile == null) || (schemaFile.length() == 0))
+      {
+        schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        attributeType.setSchemaFile(schemaFile);
+      }
+
+      modifiedSchemaFiles.add(schemaFile);
+    }
+    else
+    {
+      schema.deregisterAttributeType(existingType);
+      schema.registerAttributeType(attributeType, false);
+      schema.rebuildDependentElements(existingType);
+
+      if ((attributeType.getSchemaFile() == null) ||
+          (attributeType.getSchemaFile().length() == 0))
+      {
+        String schemaFile = existingType.getSchemaFile();
+        if ((schemaFile == null) || (schemaFile.length() == 0))
+        {
+          schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        }
+
+        attributeType.setSchemaFile(schemaFile);
+        modifiedSchemaFiles.add(schemaFile);
+      }
+      else
+      {
+        String newSchemaFile = attributeType.getSchemaFile();
+        String oldSchemaFile = existingType.getSchemaFile();
+        if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+        }
+        else
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+          modifiedSchemaFiles.add(oldSchemaFile);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required to remove the provided attribute type from
+   * the server schema, ensuring all other metadata is properly updated.  Note
+   * that this method will first check to see whether the same attribute type
+   * will be later added to the server schema with an updated definition, and if
+   * so then the removal will be ignored because the later add will be handled
+   * as a replace.  If the attribute type will not be replaced with a new
+   * definition, then this method will ensure that there are no other schema
+   * elements that depend on the attribute type before allowing it to be
+   * removed.
+   *
+   * @param  attributeType        The attribute type to remove from the server
+   *                              schema.
+   * @param  schema               The schema from which the attribute type
+   *                              should be removed.
+   * @param  modifications        The full set of modifications to be processed
+   *                              against the server schema.
+   * @param  currentPosition      The position of the modification currently
+   *                              being performed.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to remove
+   *                              the provided attribute type from the server
+   *                              schema.
+   */
+  private void removeAttributeType(AttributeType attributeType, Schema schema,
+                                   ArrayList<Modification> modifications,
+                                   int currentPosition,
+                                   Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "removeAttributeType",
+                      String.valueOf(attributeType), String.valueOf(schema),
+                      String.valueOf(modifications),
+                      String.valueOf(currentPosition),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // See if the specified attribute type is actually defined in the server
+    // schema.  If not, then fail.
+    AttributeType removeType = schema.getAttributeType(attributeType.getOID());
+    if ((removeType == null) || (! removeType.equals(attributeType)))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_ATTRIBUTE_TYPE;
+      String message = getMessage(msgID, attributeType.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // See if there is another modification later to add the attribute type back
+    // into the schema.  If so, then it's a replace and we should ignore the
+    // remove because adding it back will handle the replace.
+    for (int i=currentPosition+1; i < modifications.size(); i++)
+    {
+      Modification m = modifications.get(i);
+      Attribute    a = m.getAttribute();
+
+      if ((m.getModificationType() != ModificationType.ADD) ||
+          (! a.getAttributeType().equals(attributeTypesType)))
+      {
+        continue;
+      }
+
+      for (AttributeValue v : a.getValues())
+      {
+        AttributeType at;
+        try
+        {
+          at = AttributeTypeSyntax.decodeAttributeType(v.getValue(), schema);
+        }
+        catch (DirectoryException de)
+        {
+          assert debugException(CLASS_NAME, "removeAttributeType", de);
+
+          int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_ATTRTYPE;
+          String message = getMessage(msgID, v.getStringValue(),
+                                      de.getErrorMessage());
+          throw new DirectoryException(
+                         ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                         msgID, de);
+        }
+
+        if (attributeType.getOID().equals(at.getOID()))
+        {
+          // We found a match where the attribute type is added back later, so
+          // we don't need to do anything else here.
+          return;
+        }
+      }
+    }
+
+
+    // Make sure that the attribute type isn't used as the superior type for
+    // any other attributes.
+    for (AttributeType at : schema.getAttributeTypes().values())
+    {
+      AttributeType superiorType = at.getSuperiorType();
+      if ((superiorType != null) && superiorType.equals(removeType))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_AT_SUPERIOR_TYPE;
+        String message = getMessage(msgID, removeType.getNameOrOID(),
+                                    superiorType.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the attribute type isn't used as a required or optional
+    // attribute type in any objectclass.
+    for (ObjectClass oc : schema.getObjectClasses().values())
+    {
+      if (oc.getRequiredAttributes().contains(removeType) ||
+          oc.getOptionalAttributes().contains(removeType))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_OC;
+        String message = getMessage(msgID, removeType.getNameOrOID(),
+                                    oc.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the attribute type isn't used as a required or optional
+    // attribute type in any name form.
+    for (NameForm nf : schema.getNameFormsByObjectClass().values())
+    {
+      if (nf.getRequiredAttributes().contains(removeType) ||
+          nf.getOptionalAttributes().contains(removeType))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_NF;
+        String message = getMessage(msgID, removeType.getNameOrOID(),
+                                    nf.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the attribute type isn't used as a required, optional, or
+    // prohibited attribute type in any DIT content rule.
+    for (DITContentRule dcr : schema.getDITContentRules().values())
+    {
+      if (dcr.getRequiredAttributes().contains(removeType) ||
+          dcr.getOptionalAttributes().contains(removeType) ||
+          dcr.getProhibitedAttributes().contains(removeType))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_DCR;
+        String message = getMessage(msgID, removeType.getNameOrOID(),
+                                    dcr.getName());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the attribute type isn't referenced by any matching rule
+    // use.
+    for (MatchingRuleUse mru : schema.getMatchingRuleUses().values())
+    {
+      if (mru.getAttributes().contains(removeType))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_MR_USE;
+        String message = getMessage(msgID, removeType.getNameOrOID(),
+                                    mru.getName());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If we've gotten here, then it's OK to remove the attribute type from
+    // the schema.
+    schema.deregisterAttributeType(removeType);
+    String schemaFile = removeType.getSchemaFile();
+    if (schemaFile != null)
+    {
+      modifiedSchemaFiles.add(schemaFile);
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required for adding the provided objectclass to the
+   * given schema, replacing an existing class if necessary, and ensuring
+   * all other metadata is properly updated.
+   *
+   * @param  objectClass          The objectclass to add or replace in the
+   *                              server schema.
+   * @param  schema               The schema to which the objectclass should be
+   *                              added.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to add
+   *                              the provided objectclass to the server schema.
+   */
+  private void addObjectClass(ObjectClass objectClass, Schema schema,
+                              Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addObjectClass", String.valueOf(objectClass),
+                      String.valueOf(schema),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // First, see if the specified objectclass already exists.  We'll check the
+    // OID and all of the names, which means that it's possible there could be
+    // more than one match (although if there is, then we'll refuse the
+    // operation).
+    ObjectClass existingClass =
+         schema.getObjectClass(objectClass.getOID());
+    for (String name : objectClass.getNormalizedNames())
+    {
+      ObjectClass oc = schema.getObjectClass(name);
+      if (oc == null)
+      {
+        continue;
+      }
+      else if (existingClass == null)
+      {
+        existingClass = oc;
+      }
+      else if (existingClass != oc)
+      {
+        // NOTE:  We really do want to use "!=" instead of "! t.equals()"
+        // because we want to check whether it's the same object instance, not
+        // just a logical equivalent.
+        int msgID = MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_OBJECTCLASS;
+        String message = getMessage(msgID, objectClass.getNameOrOID(),
+                                    existingClass.getNameOrOID(),
+                                    oc.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the new objectclass doesn't reference an undefined
+    // superior class, or an undefined required or optional attribute type.
+    ObjectClass superiorClass = objectClass.getSuperiorClass();
+    if (superiorClass != null)
+    {
+      if (! schema.hasObjectClass(superiorClass.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_OBJECTCLASS;
+        String message = getMessage(msgID, objectClass.getNameOrOID(),
+                                    superiorClass.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+    for (AttributeType at : objectClass.getRequiredAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_OC_UNDEFINED_REQUIRED_ATTR;
+        String message = getMessage(msgID, objectClass.getNameOrOID(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+    for (AttributeType at : objectClass.getOptionalAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_OC_UNDEFINED_OPTIONAL_ATTR;
+        String message = getMessage(msgID, objectClass.getNameOrOID(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If there is no existing class, then we're adding a new objectclass.
+    // Otherwise, we're replacing an existing one.
+    if (existingClass == null)
+    {
+      schema.registerObjectClass(objectClass, false);
+      String schemaFile = objectClass.getSchemaFile();
+      if ((schemaFile == null) || (schemaFile.length() == 0))
+      {
+        schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        objectClass.setSchemaFile(schemaFile);
+      }
+
+      modifiedSchemaFiles.add(schemaFile);
+    }
+    else
+    {
+      schema.deregisterObjectClass(existingClass);
+      schema.registerObjectClass(objectClass, false);
+      schema.rebuildDependentElements(existingClass);
+
+      if ((objectClass.getSchemaFile() == null) ||
+          (objectClass.getSchemaFile().length() == 0))
+      {
+        String schemaFile = existingClass.getSchemaFile();
+        if ((schemaFile == null) || (schemaFile.length() == 0))
+        {
+          schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        }
+
+        objectClass.setSchemaFile(schemaFile);
+        modifiedSchemaFiles.add(schemaFile);
+      }
+      else
+      {
+        String newSchemaFile = objectClass.getSchemaFile();
+        String oldSchemaFile = existingClass.getSchemaFile();
+        if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+        }
+        else
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+          modifiedSchemaFiles.add(oldSchemaFile);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required to remove the provided objectclass from the
+   * server schema, ensuring all other metadata is properly updated.  Note that
+   * this method will first check to see whether the same objectclass will be
+   * later added to the server schema with an updated definition, and if so then
+   * the removal will be ignored because the later add will be handled as a
+   * replace.  If the objectclass will not be replaced with a new definition,
+   * then this method will ensure that there are no other schema elements that
+   * depend on the objectclass before allowing it to be removed.
+   *
+   * @param  objectClass          The objectclass to remove from the server
+   *                              schema.
+   * @param  schema               The schema from which the objectclass should
+   *                              be removed.
+   * @param  modifications        The full set of modifications to be processed
+   *                              against the server schema.
+   * @param  currentPosition      The position of the modification currently
+   *                              being performed.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to remove
+   *                              the provided objectclass from the server
+   *                              schema.
+   */
+  private void removeObjectClass(ObjectClass objectClass, Schema schema,
+                                 ArrayList<Modification> modifications,
+                                 int currentPosition,
+                                 Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "removeObjectClass",
+                      String.valueOf(objectClass), String.valueOf(schema),
+                      String.valueOf(modifications),
+                      String.valueOf(currentPosition),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // See if the specified objectclass is actually defined in the server
+    // schema.  If not, then fail.
+    ObjectClass removeClass = schema.getObjectClass(objectClass.getOID());
+    if ((removeClass == null) || (! removeClass.equals(objectClass)))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_OBJECTCLASS;
+      String message = getMessage(msgID, objectClass.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // See if there is another modification later to add the objectclass back
+    // into the schema.  If so, then it's a replace and we should ignore the
+    // remove because adding it back will handle the replace.
+    for (int i=currentPosition+1; i < modifications.size(); i++)
+    {
+      Modification m = modifications.get(i);
+      Attribute    a = m.getAttribute();
+
+      if ((m.getModificationType() != ModificationType.ADD) ||
+          (! a.getAttributeType().equals(objectClassesType)))
+      {
+        continue;
+      }
+
+      for (AttributeValue v : a.getValues())
+      {
+        ObjectClass oc;
+        try
+        {
+          oc = ObjectClassSyntax.decodeObjectClass(v.getValue(), schema);
+        }
+        catch (DirectoryException de)
+        {
+          assert debugException(CLASS_NAME, "removeObjectClass", de);
+
+          int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS;
+          String message = getMessage(msgID, v.getStringValue(),
+                                      de.getErrorMessage());
+          throw new DirectoryException(
+                         ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                         msgID, de);
+        }
+
+        if (objectClass.getOID().equals(oc.getOID()))
+        {
+          // We found a match where the objectClass is added back later, so we
+          // don't need to do anything else here.
+          return;
+        }
+      }
+    }
+
+
+    // Make sure that the objectclass isn't used as the superior class for any
+    // other objectclass.
+    for (ObjectClass oc : schema.getObjectClasses().values())
+    {
+      ObjectClass superiorClass = oc.getSuperiorClass();
+      if ((superiorClass != null) && superiorClass.equals(removeClass))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_OC_SUPERIOR_CLASS;
+        String message = getMessage(msgID, removeClass.getNameOrOID(),
+                                    superiorClass.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the objectclass isn't used as the structural class for
+    // any name form.
+    NameForm nf = schema.getNameForm(removeClass);
+    if (nf != null)
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_OC_IN_NF;
+      String message = getMessage(msgID, removeClass.getNameOrOID(),
+                                  nf.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // Make sure that the objectclass isn't used as a structural or auxiliary
+    // class for any DIT content rule.
+    for (DITContentRule dcr : schema.getDITContentRules().values())
+    {
+      if (dcr.getStructuralClass().equals(removeClass) ||
+          dcr.getAuxiliaryClasses().contains(removeClass))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_OC_IN_DCR;
+        String message = getMessage(msgID, removeClass.getNameOrOID(),
+                                    dcr.getName());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If we've gotten here, then it's OK to remove the objectclass from the
+    // schema.
+    schema.deregisterObjectClass(removeClass);
+    String schemaFile = removeClass.getSchemaFile();
+    if (schemaFile != null)
+    {
+      modifiedSchemaFiles.add(schemaFile);
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required for adding the provided name form to the
+   * the given schema, replacing an existing name form if necessary, and
+   * ensuring all other metadata is properly updated.
+   *
+   * @param  nameForm             The name form to add or replace in the server
+   *                              schema.
+   * @param  schema               The schema to which the name form should be
+   *                              added.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to add
+   *                              the provided name form to the server schema.
+   */
+  private void addNameForm(NameForm nameForm, Schema schema,
+                           Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addNameForm", String.valueOf(nameForm),
+                      String.valueOf(schema),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // First, see if the specified name form already exists.  We'll check the
+    // OID and all of the names, which means that it's possible there could be
+    // more than one match (although if there is, then we'll refuse the
+    // operation).
+    NameForm existingNF =
+         schema.getNameForm(nameForm.getOID());
+    for (String name : nameForm.getNames().keySet())
+    {
+      NameForm nf = schema.getNameForm(name);
+      if (nf == null)
+      {
+        continue;
+      }
+      else if (existingNF == null)
+      {
+        existingNF = nf;
+      }
+      else if (existingNF != nf)
+      {
+        // NOTE:  We really do want to use "!=" instead of "! t.equals()"
+        // because we want to check whether it's the same object instance, not
+        // just a logical equivalent.
+        int msgID = MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_NAME_FORM;
+        String message = getMessage(msgID, nameForm.getNameOrOID(),
+                                    existingNF.getNameOrOID(),
+                                    nf.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // Make sure that the new name form doesn't reference an undefined
+    // structural class, or an undefined required or optional attribute type.
+    ObjectClass structuralClass = nameForm.getStructuralClass();
+    if (! schema.hasObjectClass(structuralClass.getOID()))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_NF_UNDEFINED_STRUCTURAL_OC;
+      String message = getMessage(msgID, nameForm.getNameOrOID(),
+                                  structuralClass.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+    if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_NF_OC_NOT_STRUCTURAL;
+      String message = getMessage(msgID, nameForm.getNameOrOID(),
+                                  structuralClass.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+    NameForm existingNFForClass = schema.getNameForm(structuralClass);
+    if ((existingNFForClass != null) && (existingNFForClass != existingNF))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_NF;
+      String message = getMessage(msgID, nameForm.getNameOrOID(),
+                                  structuralClass.getNameOrOID(),
+                                  existingNFForClass.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+    for (AttributeType at : nameForm.getRequiredAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_NF_UNDEFINED_REQUIRED_ATTR;
+        String message = getMessage(msgID, nameForm.getNameOrOID(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+    for (AttributeType at : nameForm.getOptionalAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_NF_UNDEFINED_OPTIONAL_ATTR;
+        String message = getMessage(msgID, nameForm.getNameOrOID(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If there is no existing class, then we're adding a new name form.
+    // Otherwise, we're replacing an existing one.
+    if (existingNF == null)
+    {
+      schema.registerNameForm(nameForm, false);
+      String schemaFile = nameForm.getSchemaFile();
+      if ((schemaFile == null) || (schemaFile.length() == 0))
+      {
+        schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        nameForm.setSchemaFile(schemaFile);
+      }
+
+      modifiedSchemaFiles.add(schemaFile);
+    }
+    else
+    {
+      schema.deregisterNameForm(existingNF);
+      schema.registerNameForm(nameForm, false);
+      schema.rebuildDependentElements(existingNF);
+
+      if ((nameForm.getSchemaFile() == null) ||
+          (nameForm.getSchemaFile().length() == 0))
+      {
+        String schemaFile = existingNF.getSchemaFile();
+        if ((schemaFile == null) || (schemaFile.length() == 0))
+        {
+          schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        }
+
+        nameForm.setSchemaFile(schemaFile);
+        modifiedSchemaFiles.add(schemaFile);
+      }
+      else
+      {
+        String newSchemaFile = nameForm.getSchemaFile();
+        String oldSchemaFile = existingNF.getSchemaFile();
+        if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+        }
+        else
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+          modifiedSchemaFiles.add(oldSchemaFile);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required to remove the provided name form from the
+   * server schema, ensuring all other metadata is properly updated.  Note that
+   * this method will first check to see whether the same name form will be
+   * later added to the server schema with an updated definition, and if so then
+   * the removal will be ignored because the later add will be handled as a
+   * replace.  If the name form will not be replaced with a new definition, then
+   * this method will ensure that there are no other schema elements that depend
+   * on the name form before allowing it to be removed.
+   *
+   * @param  nameForm             The name form to remove from the server
+   *                              schema.
+   * @param  schema               The schema from which the name form should be
+   *                              be removed.
+   * @param  modifications        The full set of modifications to be processed
+   *                              against the server schema.
+   * @param  currentPosition      The position of the modification currently
+   *                              being performed.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to remove
+   *                              the provided name form from the server schema.
+   */
+  private void removeNameForm(NameForm nameForm, Schema schema,
+                              ArrayList<Modification> modifications,
+                              int currentPosition,
+                              Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "removeNameForm",
+                      String.valueOf(nameForm), String.valueOf(schema),
+                      String.valueOf(modifications),
+                      String.valueOf(currentPosition),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // See if the specified name form is actually defined in the server schema.
+    // If not, then fail.
+    NameForm removeNF = schema.getNameForm(nameForm.getOID());
+    if ((removeNF == null) || (! removeNF.equals(nameForm)))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_NAME_FORM;
+      String message = getMessage(msgID, nameForm.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // See if there is another modification later to add the name form back
+    // into the schema.  If so, then it's a replace and we should ignore the
+    // remove because adding it back will handle the replace.
+    for (int i=currentPosition+1; i < modifications.size(); i++)
+    {
+      Modification m = modifications.get(i);
+      Attribute    a = m.getAttribute();
+
+      if ((m.getModificationType() != ModificationType.ADD) ||
+          (! a.getAttributeType().equals(nameFormsType)))
+      {
+        continue;
+      }
+
+      for (AttributeValue v : a.getValues())
+      {
+        NameForm nf;
+        try
+        {
+          nf = NameFormSyntax.decodeNameForm(v.getValue(), schema);
+        }
+        catch (DirectoryException de)
+        {
+          assert debugException(CLASS_NAME, "removeNameForm", de);
+
+          int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM;
+          String message = getMessage(msgID, v.getStringValue(),
+                                      de.getErrorMessage());
+          throw new DirectoryException(
+                         ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                         msgID, de);
+        }
+
+        if (nameForm.getOID().equals(nf.getOID()))
+        {
+          // We found a match where the name form is added back later, so we
+          // don't need to do anything else here.
+          return;
+        }
+      }
+    }
+
+
+    // Make sure that the name form isn't referenced by any DIT structure
+    // rule.
+    DITStructureRule dsr = schema.getDITStructureRule(removeNF);
+    if (dsr != null)
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_NF_IN_DSR;
+      String message = getMessage(msgID, removeNF.getNameOrOID(),
+                                  dsr.getNameOrRuleID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // If we've gotten here, then it's OK to remove the name form from the
+    // schema.
+    schema.deregisterNameForm(removeNF);
+    String schemaFile = removeNF.getSchemaFile();
+    if (schemaFile != null)
+    {
+      modifiedSchemaFiles.add(schemaFile);
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required for adding the provided DIT content rule to
+   * the given schema, replacing an existing rule if necessary, and ensuring
+   * all other metadata is properly updated.
+   *
+   * @param  ditContentRule       The DIT content rule to add or replace in the
+   *                              server schema.
+   * @param  schema               The schema to which the DIT content rule
+   *                              should be be added.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to add
+   *                              the provided DIT content rule to the server
+   *                              schema.
+   */
+  private void addDITContentRule(DITContentRule ditContentRule, Schema schema,
+                                 Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addDITContentRule",
+                      String.valueOf(ditContentRule), String.valueOf(schema),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // First, see if the specified DIT content rule already exists.  We'll check
+    // all of the names, which means that it's possible there could be more than
+    // one match (although if there is, then we'll refuse the operation).
+    DITContentRule existingDCR = null;
+    for (DITContentRule dcr : schema.getDITContentRules().values())
+    {
+      for (String name : ditContentRule.getNames().keySet())
+      {
+        if (dcr.hasName(name))
+        {
+          if (existingDCR == null)
+          {
+            existingDCR = dcr;
+            break;
+          }
+          else
+          {
+            int    msgID   = MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DCR;
+            String message = getMessage(msgID, ditContentRule.getName(),
+                                        existingDCR.getName(),
+                                        dcr.getName());
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message, msgID);
+          }
+        }
+      }
+    }
+
+
+    // Get the structural class for the new DIT content rule and see if there's
+    // already an existing rule that is associated with that class.  If there
+    // is, then it will only be acceptable if it's the DIT content rule that we
+    // are replacing (in which case we really do want to use the "!=" operator).
+    ObjectClass structuralClass = ditContentRule.getStructuralClass();
+    DITContentRule existingRuleForClass =
+         schema.getDITContentRule(structuralClass);
+    if ((existingRuleForClass != null) && (existingRuleForClass != existingDCR))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_DCR;
+      String message = getMessage(msgID, ditContentRule.getName(),
+                                  structuralClass.getNameOrOID(),
+                                  existingRuleForClass.getName());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // Make sure that the new DIT content rule doesn't reference an undefined
+    // structural or auxiliaryclass, or an undefined required, optional, or
+    // prohibited attribute type.
+    if (! schema.hasObjectClass(structuralClass.getOID()))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_STRUCTURAL_OC;
+      String message = getMessage(msgID, ditContentRule.getName(),
+                                  structuralClass.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+    if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_DCR_OC_NOT_STRUCTURAL;
+      String message = getMessage(msgID, ditContentRule.getName(),
+                                  structuralClass.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+    for (ObjectClass oc : ditContentRule.getAuxiliaryClasses())
+    {
+      if (! schema.hasObjectClass(oc.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_AUXILIARY_OC;
+        String message = getMessage(msgID, ditContentRule.getName(),
+                                    oc.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+    for (AttributeType at : ditContentRule.getRequiredAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_REQUIRED_ATTR;
+        String message = getMessage(msgID, ditContentRule.getName(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+    for (AttributeType at : ditContentRule.getOptionalAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_OPTIONAL_ATTR;
+        String message = getMessage(msgID, ditContentRule.getName(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+    for (AttributeType at : ditContentRule.getProhibitedAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_PROHIBITED_ATTR;
+        String message = getMessage(msgID, ditContentRule.getName(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If there is no existing rule, then we're adding a new DIT content rule.
+    // Otherwise, we're replacing an existing one.
+    if (existingDCR == null)
+    {
+      schema.registerDITContentRule(ditContentRule, false);
+      String schemaFile = ditContentRule.getSchemaFile();
+      if ((schemaFile == null) || (schemaFile.length() == 0))
+      {
+        schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        ditContentRule.setSchemaFile(schemaFile);
+      }
+
+      modifiedSchemaFiles.add(schemaFile);
+    }
+    else
+    {
+      schema.deregisterDITContentRule(existingDCR);
+      schema.registerDITContentRule(ditContentRule, false);
+      schema.rebuildDependentElements(existingDCR);
+
+      if ((ditContentRule.getSchemaFile() == null) ||
+          (ditContentRule.getSchemaFile().length() == 0))
+      {
+        String schemaFile = existingDCR.getSchemaFile();
+        if ((schemaFile == null) || (schemaFile.length() == 0))
+        {
+          schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        }
+
+        ditContentRule.setSchemaFile(schemaFile);
+        modifiedSchemaFiles.add(schemaFile);
+      }
+      else
+      {
+        String newSchemaFile = ditContentRule.getSchemaFile();
+        String oldSchemaFile = existingDCR.getSchemaFile();
+        if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+        }
+        else
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+          modifiedSchemaFiles.add(oldSchemaFile);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required to remove the provided DIT content rule
+   * from the server schema, ensuring all other metadata is properly updated.
+   * Note that this method will first check to see whether the same rule will be
+   * later added to the server schema with an updated definition, and if so then
+   * the removal will be ignored because the later add will be handled as a
+   * replace.  If the DIT content rule will not be replaced with a new
+   * definition, then this method will ensure that there are no other schema
+   * elements that depend on the rule before allowing it to be removed.
+   *
+   * @param  ditContentRule       The DIT content rule to remove from the server
+   *                              schema.
+   * @param  schema               The schema from which the DIT content rule
+   *                              should be removed.
+   * @param  modifications        The full set of modifications to be processed
+   *                              against the server schema.
+   * @param  currentPosition      The position of the modification currently
+   *                              being performed.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to remove
+   *                              the provided DIT content rule from the server
+   *                              schema.
+   */
+  private void removeDITContentRule(DITContentRule ditContentRule,
+                                    Schema schema,
+                                    ArrayList<Modification> modifications,
+                                    int currentPosition,
+                                    Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "removeDITContentRule",
+                      String.valueOf(ditContentRule), String.valueOf(schema),
+                      String.valueOf(modifications),
+                      String.valueOf(currentPosition),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // See if the specified DIT content rule is actually defined in the server
+    // schema.  If not, then fail.
+    DITContentRule removeDCR =
+         schema.getDITContentRule(ditContentRule.getStructuralClass());
+    if ((removeDCR == null) || (! removeDCR.equals(ditContentRule)))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_DCR;
+      String message = getMessage(msgID, ditContentRule.getName());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // Since DIT content rules don't have any dependencies, then we don't need
+    // to worry about the difference between a remove or a replace.  We can
+    // just remove the DIT content rule now, and if it is added back later then
+    // there still won't be any conflict.
+    schema.deregisterDITContentRule(removeDCR);
+    String schemaFile = removeDCR.getSchemaFile();
+    if (schemaFile != null)
+    {
+      modifiedSchemaFiles.add(schemaFile);
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required for adding the provided DIT structure rule
+   * to the given schema, replacing an existing rule if necessary, and ensuring
+   * all other metadata is properly updated.
+   *
+   * @param  ditStructureRule     The DIT structure rule to add or replace in
+   *                              the server schema.
+   * @param  schema               The schema to which the DIT structure rule
+   *                              should be be added.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to add
+   *                              the provided DIT structure rule to the server
+   *                              schema.
+   */
+  private void addDITStructureRule(DITStructureRule ditStructureRule,
+                                   Schema schema,
+                                   Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addDITStructureRule",
+                      String.valueOf(ditStructureRule), String.valueOf(schema),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // First, see if the specified DIT structure rule already exists.  We'll
+    // check the rule ID and all of the names, which means that it's possible
+    // there could be more than one match (although if there is, then we'll
+    // refuse the operation).
+    DITStructureRule existingDSR =
+         schema.getDITStructureRule(ditStructureRule.getRuleID());
+    for (DITStructureRule dsr : schema.getDITStructureRulesByID().values())
+    {
+      for (String name : ditStructureRule.getNames().keySet())
+      {
+        if (dsr.hasName(name))
+        {
+          // We really do want to use the "!=" operator here because it's
+          // acceptable if we find match for the same object instance.
+          if ((existingDSR != null) && (existingDSR != dsr))
+          {
+            int    msgID   = MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DSR;
+            String message = getMessage(msgID,
+                                        ditStructureRule.getNameOrRuleID(),
+                                        existingDSR.getNameOrRuleID(),
+                                        dsr.getNameOrRuleID());
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message, msgID);
+          }
+        }
+      }
+    }
+
+
+    // Get the name form for the new DIT structure rule and see if there's
+    // already an existing rule that is associated with that name form.  If
+    // there is, then it will only be acceptable if it's the DIT structure rule
+    // that we are replacing (in which case we really do want to use the "!="
+    // operator).
+    NameForm nameForm = ditStructureRule.getNameForm();
+    DITStructureRule existingRuleForNameForm =
+         schema.getDITStructureRule(nameForm);
+    if ((existingRuleForNameForm != null) &&
+        (existingRuleForNameForm != existingDSR))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_NAME_FORM_CONFLICT_FOR_ADD_DSR;
+      String message = getMessage(msgID, ditStructureRule.getNameOrRuleID(),
+                                  nameForm.getNameOrOID(),
+                                  existingRuleForNameForm.getNameOrRuleID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // Make sure that the new DIT structure rule doesn't reference an undefined
+    // name form or superior DIT structure rule.
+    if (! schema.hasNameForm(nameForm.getOID()))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_DSR_UNDEFINED_NAME_FORM;
+      String message = getMessage(msgID, ditStructureRule.getNameOrRuleID(),
+                                  nameForm.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // If there is no existing rule, then we're adding a new DIT structure rule.
+    // Otherwise, we're replacing an existing one.
+    if (existingDSR == null)
+    {
+      schema.registerDITStructureRule(ditStructureRule, false);
+      String schemaFile = ditStructureRule.getSchemaFile();
+      if ((schemaFile == null) || (schemaFile.length() == 0))
+      {
+        schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        ditStructureRule.setSchemaFile(schemaFile);
+      }
+
+      modifiedSchemaFiles.add(schemaFile);
+    }
+    else
+    {
+      schema.deregisterDITStructureRule(existingDSR);
+      schema.registerDITStructureRule(ditStructureRule, false);
+      schema.rebuildDependentElements(existingDSR);
+
+      if ((ditStructureRule.getSchemaFile() == null) ||
+          (ditStructureRule.getSchemaFile().length() == 0))
+      {
+        String schemaFile = existingDSR.getSchemaFile();
+        if ((schemaFile == null) || (schemaFile.length() == 0))
+        {
+          schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        }
+
+        ditStructureRule.setSchemaFile(schemaFile);
+        modifiedSchemaFiles.add(schemaFile);
+      }
+      else
+      {
+        String newSchemaFile = ditStructureRule.getSchemaFile();
+        String oldSchemaFile = existingDSR.getSchemaFile();
+        if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+        }
+        else
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+          modifiedSchemaFiles.add(oldSchemaFile);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required to remove the provided DIT structure rule
+   * from the server schema, ensuring all other metadata is properly updated.
+   * Note that this method will first check to see whether the same rule will be
+   * later added to the server schema with an updated definition, and if so then
+   * the removal will be ignored because the later add will be handled as a
+   * replace.  If the DIT structure rule will not be replaced with a new
+   * definition, then this method will ensure that there are no other schema
+   * elements that depend on the rule before allowing it to be removed.
+   *
+   * @param  ditStructureRule     The DIT structure rule to remove from the
+   *                              server schema.
+   * @param  schema               The schema from which the DIT structure rule
+   *                              should be removed.
+   * @param  modifications        The full set of modifications to be processed
+   *                              against the server schema.
+   * @param  currentPosition      The position of the modification currently
+   *                              being performed.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to remove
+   *                              the provided DIT structure rule from the
+   *                              server schema.
+   */
+  private void removeDITStructureRule(DITStructureRule ditStructureRule,
+                                      Schema schema,
+                                      ArrayList<Modification> modifications,
+                                      int currentPosition,
+                                      Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "removeDITStructureRule",
+                      String.valueOf(ditStructureRule), String.valueOf(schema),
+                      String.valueOf(modifications),
+                      String.valueOf(currentPosition),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // See if the specified DIT structure rule is actually defined in the server
+    // schema.  If not, then fail.
+    DITStructureRule removeDSR =
+         schema.getDITStructureRule(ditStructureRule.getRuleID());
+    if ((removeDSR == null) || (! removeDSR.equals(ditStructureRule)))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_DSR;
+      String message = getMessage(msgID, ditStructureRule.getNameOrRuleID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // See if there is another modification later to add the DIT structure rule
+    // back into the schema.  If so, then it's a replace and we should ignore
+    // the remove because adding it back will handle the replace.
+    for (int i=currentPosition+1; i < modifications.size(); i++)
+    {
+      Modification m = modifications.get(i);
+      Attribute    a = m.getAttribute();
+
+      if ((m.getModificationType() != ModificationType.ADD) ||
+          (! a.getAttributeType().equals(ditStructureRulesType)))
+      {
+        continue;
+      }
+
+      for (AttributeValue v : a.getValues())
+      {
+        DITStructureRule dsr;
+        try
+        {
+          dsr = DITStructureRuleSyntax.decodeDITStructureRule(
+                     v.getValue(), schema, false);
+        }
+        catch (DirectoryException de)
+        {
+          assert debugException(CLASS_NAME, "removeDITStructureRule", de);
+
+          int msgID = MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DSR;
+          String message = getMessage(msgID, v.getStringValue(),
+                                      de.getErrorMessage());
+          throw new DirectoryException(
+                         ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                         msgID, de);
+        }
+
+        if (ditStructureRule.getRuleID() == dsr.getRuleID())
+        {
+          // We found a match where the DIT structure rule is added back later,
+          // so we don't need to do anything else here.
+          return;
+        }
+      }
+    }
+
+
+    // Make sure that the DIT structure rule isn't the superior for any other
+    // DIT structure rule.
+    for (DITStructureRule dsr : schema.getDITStructureRulesByID().values())
+    {
+      if (dsr.getSuperiorRules().contains(removeDSR))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_DSR_SUPERIOR_RULE;
+        String message = getMessage(msgID, removeDSR.getNameOrRuleID(),
+                                    dsr.getNameOrRuleID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If we've gotten here, then it's OK to remove the DIT structure rule from
+    // the schema.
+    schema.deregisterDITStructureRule(removeDSR);
+    String schemaFile = removeDSR.getSchemaFile();
+    if (schemaFile != null)
+    {
+      modifiedSchemaFiles.add(schemaFile);
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required for adding the provided matching rule use
+   * to the given schema, replacing an existing use if necessary, and ensuring
+   * all other metadata is properly updated.
+   *
+   * @param  matchingRuleUse      The matching rule use to add or replace in the
+   *                              server schema.
+   * @param  schema               The schema to which the matching rule use
+   *                              should be added.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to add
+   *                              the provided matching rule use to the server
+   *                              schema.
+   */
+  private void addMatchingRuleUse(MatchingRuleUse matchingRuleUse,
+                                  Schema schema,
+                                  Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addMatchingRuleUse",
+                      String.valueOf(matchingRuleUse), String.valueOf(schema),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // First, see if the specified matching rule use already exists.  We'll
+    // check all of the names, which means that it's possible that there could
+    // be more than one match (although if there is, then we'll refuse the
+    // operation).
+    MatchingRuleUse existingMRU = null;
+    for (MatchingRuleUse mru : schema.getMatchingRuleUses().values())
+    {
+      for (String name : matchingRuleUse.getNames().keySet())
+      {
+        if (mru.hasName(name))
+        {
+          if (existingMRU == null)
+          {
+            existingMRU = mru;
+            break;
+          }
+          else
+          {
+            int msgID = MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_MR_USE;
+            String message = getMessage(msgID, matchingRuleUse.getName(),
+                                        existingMRU.getName(), mru.getName());
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message, msgID);
+          }
+        }
+      }
+    }
+
+
+    // Get the matching rule for the new matching rule use and see if there's
+    // already an existing matching rule use that is associated with that
+    // matching rule.  If there is, then it will only be acceptable if it's the
+    // matching rule use that we are replacing (in which case we really do want
+    // to use the "!=" operator).
+    MatchingRule matchingRule = matchingRuleUse.getMatchingRule();
+    MatchingRuleUse existingMRUForRule =
+         schema.getMatchingRuleUse(matchingRule);
+    if ((existingMRUForRule != null) && (existingMRUForRule != existingMRU))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_MR_CONFLICT_FOR_ADD_MR_USE;
+      String message = getMessage(msgID, matchingRuleUse.getName(),
+                                  matchingRule.getNameOrOID(),
+                                  existingMRUForRule.getName());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // Make sure that the new matching rule use doesn't reference an undefined
+    // attribute type.
+    for (AttributeType at : matchingRuleUse.getAttributes())
+    {
+      if (! schema.hasAttributeType(at.getOID()))
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_MRU_UNDEFINED_ATTR;
+        String message = getMessage(msgID, matchingRuleUse.getName(),
+                                    at.getNameOrOID());
+        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                     msgID);
+      }
+    }
+
+
+    // If there is no existing matching rule use, then we're adding a new one.
+    // Otherwise, we're replacing an existing matching rule use.
+    if (existingMRU == null)
+    {
+      schema.registerMatchingRuleUse(matchingRuleUse, false);
+      String schemaFile = matchingRuleUse.getSchemaFile();
+      if ((schemaFile == null) || (schemaFile.length() == 0))
+      {
+        schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        matchingRuleUse.setSchemaFile(schemaFile);
+      }
+
+      modifiedSchemaFiles.add(schemaFile);
+    }
+    else
+    {
+      schema.deregisterMatchingRuleUse(existingMRU);
+      schema.registerMatchingRuleUse(matchingRuleUse, false);
+      schema.rebuildDependentElements(existingMRU);
+
+      if ((matchingRuleUse.getSchemaFile() == null) ||
+          (matchingRuleUse.getSchemaFile().length() == 0))
+      {
+        String schemaFile = existingMRU.getSchemaFile();
+        if ((schemaFile == null) || (schemaFile.length() == 0))
+        {
+          schemaFile = FILE_USER_SCHEMA_ELEMENTS;
+        }
+
+        matchingRuleUse.setSchemaFile(schemaFile);
+        modifiedSchemaFiles.add(schemaFile);
+      }
+      else
+      {
+        String newSchemaFile = matchingRuleUse.getSchemaFile();
+        String oldSchemaFile = existingMRU.getSchemaFile();
+        if ((oldSchemaFile == null) || oldSchemaFile.equals(newSchemaFile))
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+        }
+        else
+        {
+          modifiedSchemaFiles.add(newSchemaFile);
+          modifiedSchemaFiles.add(oldSchemaFile);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Handles all processing required to remove the provided matching rule use
+   * from the server schema, ensuring all other metadata is properly updated.
+   * Note that this method will first check to see whether the same matching
+   * rule use will be later added to the server schema with an updated
+   * definition, and if so then the removal will be ignored because the later
+   * add will be handled as a replace.  If the matching rule use will not be
+   * replaced with a new definition, then this method will ensure that there are
+   * no other schema elements that depend on the matching rule use before
+   * allowing it to be removed.
+   *
+   * @param  matchingRuleUse      The matching rule use to remove from the
+   *                              server schema.
+   * @param  schema               The schema from which the matching rule use
+   *                              should be removed.
+   * @param  modifications        The full set of modifications to be processed
+   *                              against the server schema.
+   * @param  currentPosition      The position of the modification currently
+   *                              being performed.
+   * @param  modifiedSchemaFiles  The names of the schema files containing
+   *                              schema elements that have been updated as part
+   *                              of the schema modification.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to remove
+   *                              the provided matching rule use from the server
+   *                              schema.
+   */
+  private void removeMatchingRuleUse(MatchingRuleUse matchingRuleUse,
+                                     Schema schema,
+                                     ArrayList<Modification> modifications,
+                                     int currentPosition,
+                                     Set<String> modifiedSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "removeMatchingRuleUse",
+                      String.valueOf(matchingRuleUse), String.valueOf(schema),
+                      String.valueOf(modifications),
+                      String.valueOf(currentPosition),
+                      String.valueOf(modifiedSchemaFiles));
+
+
+    // See if the specified DIT content rule is actually defined in the server
+    // schema.  If not, then fail.
+    MatchingRuleUse removeMRU =
+         schema.getMatchingRuleUse(matchingRuleUse.getMatchingRule());
+    if ((removeMRU == null) || (! removeMRU.equals(matchingRuleUse)))
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_MR_USE;
+      String message = getMessage(msgID, matchingRuleUse.getName());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+
+    // Since matching rule uses don't have any dependencies, then we don't need
+    // to worry about the difference between a remove or a replace.  We can
+    // just remove the DIT content rule now, and if it is added back later then
+    // there still won't be any conflict.
+    schema.deregisterMatchingRuleUse(removeMRU);
+    String schemaFile = removeMRU.getSchemaFile();
+    if (schemaFile != null)
+    {
+      modifiedSchemaFiles.add(schemaFile);
+    }
   }
 
 
@@ -1215,6 +2961,661 @@
 
 
 
+
+  /**
+   * Writes a temporary version of the specified schema file.
+   *
+   * @param  schema      The schema from which to take the definitions to be
+   *                     written.
+   * @param  schemaFile  The name of the schema file to be written.
+   *
+   * @throws  DirectoryException  If an unexpected problem occurs while
+   *                              identifying the schema definitions to include
+   *                              in the schema file.
+   *
+   * @throws  IOException  If an unexpected error occurs while attempting to
+   *                       write the temporary schema file.
+   *
+   * @throws  LDIFException  If an unexpected problem occurs while generating
+   *                         the LDIF representation of the schema entry.
+   */
+  private File writeTempSchemaFile(Schema schema, String schemaFile)
+          throws DirectoryException, IOException, LDIFException
+  {
+    assert debugEnter(CLASS_NAME, "writeTempSchemaFile", String.valueOf(schema),
+                      String.valueOf(schemaFile));
+
+
+    // Start with an empty schema entry.
+    Entry schemaEntry = createEmptySchemaEntry();
+
+
+    // Add all of the appropriate attribute types to the schema entry.  We need
+    // to be careful of the ordering to ensure that any superior types in the
+    // same file are written before the subordinate types.
+    HashSet<AttributeType> addedTypes = new HashSet<AttributeType>();
+    LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
+    for (AttributeType at : schema.getAttributeTypes().values())
+    {
+      if (schemaFile.equals(at.getSchemaFile()))
+      {
+        addAttrTypeToSchemaFile(schema, schemaFile, at, values, addedTypes, 0);
+      }
+    }
+
+    if (! values.isEmpty())
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(new Attribute(attributeTypesType,
+                                 attributeTypesType.getPrimaryName(), values));
+      schemaEntry.putAttribute(attributeTypesType, attrList);
+    }
+
+
+    // Add all of the appropriate objectclasses to the schema entry.  We need
+    // to be careful of the ordering to ensure that any superior classes in the
+    // same file are written before the subordinate classes.
+    HashSet<ObjectClass> addedClasses = new HashSet<ObjectClass>();
+    values = new LinkedHashSet<AttributeValue>();
+    for (ObjectClass oc : schema.getObjectClasses().values())
+    {
+      if (schemaFile.equals(oc.getSchemaFile()))
+      {
+        addObjectClassToSchemaFile(schema, schemaFile, oc, values, addedClasses,
+                                   0);
+      }
+    }
+
+    if (! values.isEmpty())
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(new Attribute(objectClassesType,
+                                 objectClassesType.getPrimaryName(), values));
+      schemaEntry.putAttribute(objectClassesType, attrList);
+    }
+
+
+    // Add all of the appropriate name forms to the schema entry.  Since there
+    // is no hierarchical relationship between name forms, we don't need to
+    // worry about ordering.
+    values = new LinkedHashSet<AttributeValue>();
+    for (NameForm nf : schema.getNameFormsByObjectClass().values())
+    {
+      if (schemaFile.equals(nf.getSchemaFile()))
+      {
+        values.add(new AttributeValue(nameFormsType, nf.getDefinition()));
+      }
+    }
+
+    if (! values.isEmpty())
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(new Attribute(nameFormsType,
+                                 nameFormsType.getPrimaryName(), values));
+      schemaEntry.putAttribute(nameFormsType, attrList);
+    }
+
+
+    // Add all of the appropriate DIT content rules to the schema entry.  Since
+    // there is no hierarchical relationship between DIT content rules, we don't
+    // need to worry about ordering.
+    values = new LinkedHashSet<AttributeValue>();
+    for (DITContentRule dcr : schema.getDITContentRules().values())
+    {
+      if (schemaFile.equals(dcr.getSchemaFile()))
+      {
+        values.add(new AttributeValue(ditContentRulesType,
+                                      dcr.getDefinition()));
+      }
+    }
+
+    if (! values.isEmpty())
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(new Attribute(ditContentRulesType,
+                                 ditContentRulesType.getPrimaryName(), values));
+      schemaEntry.putAttribute(ditContentRulesType, attrList);
+    }
+
+
+    // Add all of the appropriate DIT structure rules to the schema entry.  We
+    // need to be careful of the ordering to ensure that any superior rules in
+    // the same file are written before the subordinate rules.
+    HashSet<DITStructureRule> addedDSRs = new HashSet<DITStructureRule>();
+    values = new LinkedHashSet<AttributeValue>();
+    for (DITStructureRule dsr : schema.getDITStructureRulesByID().values())
+    {
+      if (schemaFile.equals(dsr.getSchemaFile()))
+      {
+        addDITStructureRuleToSchemaFile(schema, schemaFile, dsr, values,
+                                        addedDSRs, 0);
+      }
+    }
+
+    if (! values.isEmpty())
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(new Attribute(ditStructureRulesType,
+                                 ditStructureRulesType.getPrimaryName(),
+                                 values));
+      schemaEntry.putAttribute(ditStructureRulesType, attrList);
+    }
+
+
+    // Add all of the appropriate matching rule uses to the schema entry.  Since
+    // there is no hierarchical relationship between matching rule uses, we
+    // don't need to worry about ordering.
+    values = new LinkedHashSet<AttributeValue>();
+    for (MatchingRuleUse mru : schema.getMatchingRuleUses().values())
+    {
+      if (schemaFile.equals(mru.getSchemaFile()))
+      {
+        values.add(new AttributeValue(matchingRuleUsesType,
+                                      mru.getDefinition()));
+      }
+    }
+
+    if (! values.isEmpty())
+    {
+      ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+      attrList.add(new Attribute(matchingRuleUsesType,
+                                 matchingRuleUsesType.getPrimaryName(),
+                                 values));
+      schemaEntry.putAttribute(matchingRuleUsesType, attrList);
+    }
+
+
+    // Create a temporary file to which we can write the schema entry.
+    File tempFile = File.createTempFile(schemaFile, "temp");
+    LDIFExportConfig exportConfig =
+         new LDIFExportConfig(tempFile.getAbsolutePath(),
+                              ExistingFileBehavior.OVERWRITE);
+    LDIFWriter ldifWriter = new LDIFWriter(exportConfig);
+    ldifWriter.writeEntry(schemaEntry);
+    ldifWriter.close();
+
+    return tempFile;
+  }
+
+
+
+  /**
+   * Adds the definition for the specified attribute type to the provided set of
+   * attribute values, recursively adding superior types as appropriate.
+   *
+   * @param  schema         The schema containing the attribute type.
+   * @param  schemaFile     The schema file with which the attribute type is
+   *                        associated.
+   * @param  attributeType  The attribute type whose definition should be added
+   *                        to the value set.
+   * @param  values         The set of values for attribute type definitions
+   *                        already added.
+   * @param  addedTypes     The set of attribute types whose definitions have
+   *                        already been added to the set of values.
+   * @param  depth          A depth counter to use in an attempt to detect
+   *                        circular references.
+   */
+  private void addAttrTypeToSchemaFile(Schema schema, String schemaFile,
+                                       AttributeType attributeType,
+                                       LinkedHashSet<AttributeValue> values,
+                                       HashSet<AttributeType> addedTypes,
+                                       int depth)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addAttrTypeToSchemaFile",
+                      String.valueOf(schema), String.valueOf(schemaFile),
+                      String.valueOf(attributeType), String.valueOf(values),
+                      String.valueOf(addedTypes), String.valueOf(depth));
+
+    if (depth > 20)
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_AT;
+      String message = getMessage(msgID, attributeType.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+    if (addedTypes.contains(attributeType))
+    {
+      return;
+    }
+
+    AttributeType superiorType = attributeType.getSuperiorType();
+    if ((superiorType != null) &&
+        schemaFile.equals(superiorType.getSchemaFile()) &&
+        (! addedTypes.contains(superiorType)))
+    {
+      addAttrTypeToSchemaFile(schema, schemaFile, superiorType, values,
+                              addedTypes, depth+1);
+    }
+
+    values.add(new AttributeValue(attributeTypesType,
+                                  attributeType.getDefinition()));
+    addedTypes.add(attributeType);
+  }
+
+
+
+  /**
+   * Adds the definition for the specified objectclass to the provided set of
+   * attribute values, recursively adding superior classes as appropriate.
+   *
+   * @param  schema        The schema containing the objectclass.
+   * @param  schemaFile    The schema file with which the objectclass is
+   *                       associated.
+   * @param  objectClass   The objectclass whose definition should be added to
+   *                       the value set.
+   * @param  values        The set of values for objectclass definitions
+   *                       already added.
+   * @param  addedClasses  The set of objectclasses whose definitions have
+   *                       already been added to the set of values.
+   * @param  depth         A depth counter to use in an attempt to detect
+   *                       circular references.
+   */
+  private void addObjectClassToSchemaFile(Schema schema, String schemaFile,
+                                          ObjectClass objectClass,
+                                          LinkedHashSet<AttributeValue> values,
+                                          HashSet<ObjectClass> addedClasses,
+                                          int depth)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addObjectClassToSchemaFile",
+                      String.valueOf(schema), String.valueOf(schemaFile),
+                      String.valueOf(objectClass), String.valueOf(values),
+                      String.valueOf(addedClasses), String.valueOf(depth));
+
+    if (depth > 20)
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_OC;
+      String message = getMessage(msgID, objectClass.getNameOrOID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+    if (addedClasses.contains(objectClass))
+    {
+      return;
+    }
+
+    ObjectClass superiorClass = objectClass.getSuperiorClass();
+    if ((superiorClass != null) &&
+        schemaFile.equals(superiorClass.getSchemaFile()) &&
+        (! addedClasses.contains(superiorClass)))
+    {
+      addObjectClassToSchemaFile(schema, schemaFile, superiorClass, values,
+                                 addedClasses, depth+1);
+    }
+
+    values.add(new AttributeValue(objectClassesType,
+                                  objectClass.getDefinition()));
+    addedClasses.add(objectClass);
+  }
+
+
+
+  /**
+   * Adds the definition for the specified DIT structure rule to the provided
+   * set of attribute values, recursively adding superior rules as appropriate.
+   *
+   * @param  schema            The schema containing the DIT structure rule.
+   * @param  schemaFile        The schema file with which the DIT structure rule
+   *                           is associated.
+   * @param  ditStructureRule  The DIT structure rule whose definition should be
+   *                           added to the value set.
+   * @param  values            The set of values for DIT structure rule
+   *                           definitions already added.
+   * @param  addedDSRs         The set of DIT structure rules whose definitions
+   *                           have already been added added to the set of
+   *                           values.
+   * @param  depth             A depth counter to use in an attempt to detect
+   *                           circular references.
+   */
+  private void addDITStructureRuleToSchemaFile(Schema schema, String schemaFile,
+                    DITStructureRule ditStructureRule,
+                    LinkedHashSet<AttributeValue> values,
+                    HashSet<DITStructureRule> addedDSRs, int depth)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "addDITStructureRuleToSchemaFile",
+                      String.valueOf(schema), String.valueOf(schemaFile),
+                      String.valueOf(ditStructureRule), String.valueOf(values),
+                      String.valueOf(addedDSRs), String.valueOf(depth));
+
+    if (depth > 20)
+    {
+      int    msgID   = MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_DSR;
+      String message = getMessage(msgID, ditStructureRule.getNameOrRuleID());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
+                                   msgID);
+    }
+
+    if (addedDSRs.contains(ditStructureRule))
+    {
+      return;
+    }
+
+    for (DITStructureRule dsr : ditStructureRule.getSuperiorRules())
+    {
+      if (schemaFile.equals(dsr.getSchemaFile()) && (! addedDSRs.contains(dsr)))
+      {
+        addDITStructureRuleToSchemaFile(schema, schemaFile, dsr, values,
+                                        addedDSRs, depth+1);
+      }
+    }
+
+    values.add(new AttributeValue(ditStructureRulesType,
+                                  ditStructureRule.getDefinition()));
+    addedDSRs.add(ditStructureRule);
+  }
+
+
+
+  /**
+   * Moves the specified temporary schema files in place of the active versions.
+   * If an error occurs in the process, then this method will attempt to restore
+   * the original schema files if possible.
+   *
+   * @param  tempSchemaFiles  The set of temporary schema files to be activated.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting to
+   *                              install the temporary schema files.
+   */
+  private void installSchemaFiles(HashMap<String,File> tempSchemaFiles)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "installSchemaFiles",
+                      String.valueOf(tempSchemaFiles));
+
+
+    // Create lists that will hold the three types of files we'll be dealing
+    // with (the temporary files that will be installed, the installed schema
+    // files, and the previously-installed schema files).
+    ArrayList<File> installedFileList = new ArrayList<File>();
+    ArrayList<File> tempFileList      = new ArrayList<File>();
+    ArrayList<File> origFileList      = new ArrayList<File>();
+
+    File schemaDir = new File(SchemaConfigManager.getSchemaDirectoryPath());
+
+    for (String name : tempSchemaFiles.keySet())
+    {
+      installedFileList.add(new File(schemaDir, name));
+      tempFileList.add(tempSchemaFiles.get(name));
+      origFileList.add(new File(schemaDir, name + ".orig"));
+    }
+
+
+    // If there are any old ".orig" files laying around from a previous
+    // attempt, then try to clean them up.
+    for (File f : origFileList)
+    {
+      if (f.exists())
+      {
+        f.delete();
+      }
+    }
+
+
+    // Copy all of the currently-installed files with a ".orig" extension.  If
+    // this fails, then try to clean up the copies.
+    try
+    {
+      for (int i=0; i < installedFileList.size(); i++)
+      {
+        File installedFile = installedFileList.get(i);
+        File origFile      = origFileList.get(i);
+
+        if (installedFile.exists())
+        {
+          copyFile(installedFile, origFile);
+        }
+      }
+    }
+    catch (Exception e)
+    {
+      assert debugException(CLASS_NAME, "installSchemaFiles", e);
+
+      boolean allCleaned = true;
+      for (File f : origFileList)
+      {
+        try
+        {
+          if (f.exists())
+          {
+            if (! f.delete())
+            {
+              allCleaned = false;
+            }
+          }
+        }
+        catch (Exception e2)
+        {
+          assert debugException(CLASS_NAME, "installSchemaFiles", e2);
+
+          allCleaned = false;
+        }
+      }
+
+      if (allCleaned)
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_CLEANED;
+        String message = getMessage(msgID, stackTraceToSingleLineString(e));
+        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+                                     message, msgID, e);
+      }
+      else
+      {
+        int msgID = MSGID_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_NOT_CLEANED;
+        String message = getMessage(msgID, stackTraceToSingleLineString(e));
+
+        DirectoryServer.sendAlertNotification(this,
+                             ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES, msgID,
+                             message);
+
+        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+                                     message, msgID, e);
+      }
+    }
+
+
+    // Try to copy all of the temporary files into place over the installed
+    // files.  If this fails, then try to restore the originals.
+    try
+    {
+      for (int i=0; i < installedFileList.size(); i++)
+      {
+        File installedFile = installedFileList.get(i);
+        File tempFile      = tempFileList.get(i);
+        copyFile(tempFile, installedFile);
+      }
+    }
+    catch (Exception e)
+    {
+      assert debugException(CLASS_NAME, "installSchemaFiles", e);
+
+      for (File f : installedFileList)
+      {
+        try
+        {
+          if (f.exists())
+          {
+            f.delete();
+          }
+        }
+        catch (Exception e2)
+        {
+          assert debugException(CLASS_NAME, "installSchemaFiles", e2);
+        }
+      }
+
+      boolean allRestored = true;
+      for (int i=0; i < installedFileList.size(); i++)
+      {
+        File installedFile = installedFileList.get(i);
+        File origFile      = origFileList.get(i);
+
+        try
+        {
+          if (origFile.exists())
+          {
+            if (! origFile.renameTo(installedFile))
+            {
+              allRestored = false;
+            }
+          }
+        }
+        catch (Exception e2)
+        {
+          assert debugException(CLASS_NAME, "installSchemaFiles", e2);
+
+          allRestored = false;
+        }
+      }
+
+      if (allRestored)
+      {
+        int    msgID   = MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_RESTORED;
+        String message = getMessage(msgID, stackTraceToSingleLineString(e));
+        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+                                     message, msgID, e);
+      }
+      else
+      {
+        int msgID = MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED;
+        String message = getMessage(msgID, stackTraceToSingleLineString(e));
+
+        DirectoryServer.sendAlertNotification(this,
+                             ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES, msgID,
+                             message);
+
+        throw new DirectoryException(DirectoryServer.getServerErrorResultCode(),
+                                     message, msgID, e);
+      }
+    }
+
+
+    // At this point, we're committed to the schema change, so we can't throw
+    // any more exceptions, but all we have left is to clean up the original and
+    // temporary files.
+    for (File f : origFileList)
+    {
+      try
+      {
+        if (f.exists())
+        {
+          f.delete();
+        }
+      }
+      catch (Exception e)
+      {
+        assert debugException(CLASS_NAME, "installSchemaFiles", e);
+      }
+    }
+
+    for (File f : tempFileList)
+    {
+      try
+      {
+        if (f.exists())
+        {
+          f.delete();
+        }
+      }
+      catch (Exception e)
+      {
+        assert debugException(CLASS_NAME, "installSchemaFiles", e);
+      }
+    }
+  }
+
+
+
+  /**
+   * Creates a copy of the specified file.
+   *
+   * @param  from  The source file to be copied.
+   * @param  to    The destination file to be created.
+   *
+   * @throws  IOException  If a problem occurs.
+   */
+  private void copyFile(File from, File to)
+          throws IOException
+  {
+    assert debugEnter(CLASS_NAME, "copyFile", String.valueOf(from),
+                      String.valueOf(to));
+
+    byte[]           buffer        = new byte[4096];
+    FileInputStream  inputStream   = null;
+    FileOutputStream outputStream  = null;
+    try
+    {
+      inputStream  = new FileInputStream(from);
+      outputStream = new FileOutputStream(to, false);
+
+      int bytesRead = inputStream.read(buffer);
+      while (bytesRead > 0)
+      {
+        outputStream.write(buffer, 0, bytesRead);
+        bytesRead = inputStream.read(buffer);
+      }
+    }
+    finally
+    {
+      if (inputStream != null)
+      {
+        try
+        {
+          inputStream.close();
+        }
+        catch (Exception e)
+        {
+          assert debugException(CLASS_NAME, "copyFile", e);
+        }
+      }
+
+      if (outputStream != null)
+      {
+        outputStream.close();
+      }
+    }
+  }
+
+
+
+  /**
+   * Performs any necessary cleanup in an attempt to delete any temporary schema
+   * files that may have been left over after trying to install the new schema.
+   *
+   * @param  tempSchemaFiles  The set of temporary schema files that have been
+   *                          created and are candidates for cleanup.
+   */
+  private void cleanUpTempSchemaFiles(HashMap<String,File> tempSchemaFiles)
+  {
+    assert debugEnter(CLASS_NAME, "cleanUpTempSchemaFiles",
+                      String.valueOf(tempSchemaFiles));
+
+    if ((tempSchemaFiles == null) || tempSchemaFiles.isEmpty())
+    {
+      return;
+    }
+
+    for (File f : tempSchemaFiles.values())
+    {
+      try
+      {
+        if (f.exists())
+        {
+          f.delete();
+        }
+      }
+      catch (Exception e)
+      {
+        assert debugException(CLASS_NAME, "cleanUpTempSchemaFiles", e);
+      }
+    }
+  }
+
+
+
   /**
    * Moves and/or renames the provided entry in this backend, altering any
    * subordinate entries as necessary.  This must ensure that an entry already
@@ -2795,5 +5196,63 @@
 
     this.showAllAttributes = showAllAttributes;
   }
+
+
+
+  /**
+   * Retrieves the DN of the configuration entry with which this alert generator
+   * is associated.
+   *
+   * @return  The DN of the configuration entry with which this alert generator
+   *          is associated.
+   */
+  public DN getComponentEntryDN()
+  {
+    assert debugEnter(CLASS_NAME, "getComponentEntryDN");
+
+    return configEntryDN;
+  }
+
+
+
+  /**
+   * Retrieves the fully-qualified name of the Java class for this alert
+   * generator implementation.
+   *
+   * @return  The fully-qualified name of the Java class for this alert
+   *          generator implementation.
+   */
+  public String getClassName()
+  {
+    assert debugEnter(CLASS_NAME, "getClassName");
+
+    return CLASS_NAME;
+  }
+
+
+
+  /**
+   * Retrieves information about the set of alerts that this generator may
+   * produce.  The map returned should be between the notification type for a
+   * particular notification and the human-readable description for that
+   * notification.  This alert generator must not generate any alerts with types
+   * that are not contained in this list.
+   *
+   * @return  Information about the set of alerts that this generator may
+   *          produce.
+   */
+  public LinkedHashMap<String,String> getAlerts()
+  {
+    assert debugEnter(CLASS_NAME, "getAlerts");
+
+    LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
+
+    alerts.put(ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES,
+               ALERT_DESCRIPTION_CANNOT_COPY_SCHEMA_FILES);
+    alerts.put(ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES,
+               ALERT_DESCRIPTION_CANNOT_WRITE_NEW_SCHEMA_FILES);
+
+    return alerts;
+  }
 }
 
diff --git a/opends/src/server/org/opends/server/core/DirectoryServer.java b/opends/src/server/org/opends/server/core/DirectoryServer.java
index 6817cbc..f9bb224 100644
--- a/opends/src/server/org/opends/server/core/DirectoryServer.java
+++ b/opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.core;
 
@@ -2905,10 +2905,16 @@
          directoryServer.schema.getObjectClass(TOP_OBJECTCLASS_NAME);
     if (objectClass == null)
     {
-      objectClass = new ObjectClass(TOP_OBJECTCLASS_NAME, Collections
-          .singleton(TOP_OBJECTCLASS_NAME), TOP_OBJECTCLASS_OID,
-          TOP_OBJECTCLASS_DESCRIPTION, null, null, null,
-          ObjectClassType.ABSTRACT, false, null);
+      String definition =
+           "( 2.5.6.0 NAME 'top' ABSTRACT MUST objectClass " +
+           "X-ORIGIN 'RFC 2256' )";
+
+      objectClass = new ObjectClass(definition, TOP_OBJECTCLASS_NAME,
+                                    Collections.singleton(TOP_OBJECTCLASS_NAME),
+                                    TOP_OBJECTCLASS_OID,
+                                    TOP_OBJECTCLASS_DESCRIPTION, null, null,
+                                    null, ObjectClassType.ABSTRACT, false,
+                                    null);
     }
 
     return objectClass;
@@ -2937,10 +2943,13 @@
     ObjectClass objectClass = directoryServer.schema.getObjectClass(lowerName);
     if (objectClass == null)
     {
-      objectClass = new ObjectClass(name,
-          Collections.singleton(name), lowerName, null,
-          getTopObjectClass(), null, null, ObjectClassType.ABSTRACT,
-          false, null);
+      String oid        = lowerName + "-oid";
+      String definition = "( " + oid + " NAME '" + name + "' ABSTRACT )";
+
+      objectClass = new ObjectClass(definition, name,
+                                    Collections.singleton(name), oid, null,
+                                    getTopObjectClass(), null, null,
+                                    ObjectClassType.ABSTRACT, false, null);
     }
 
     return objectClass;
@@ -3109,8 +3118,12 @@
           }
         }
 
+        String definition =
+             "( 2.5.4.0 NAME 'objectClass' EQUALITY objectIdentifierMatch " +
+             "SYNTAX 1.3.6.1.4.1.1466.115.121.1.38 X-ORIGIN 'RFC 2256' )";
+
         directoryServer.objectClassAttributeType =
-             new AttributeType("objectClass",
+             new AttributeType(definition, "objectClass",
                                Collections.singleton("objectClass"),
                                OBJECTCLASS_ATTRIBUTE_TYPE_OID, null, null,
                                oidSyntax, AttributeUsage.USER_APPLICATIONS,
@@ -3172,9 +3185,12 @@
                       String.valueOf(name));
 
 
-    String lowerName = toLowerCase(name);
-    return new AttributeType(name, Collections.singleton(name),
-                             lowerName, null, null, syntax,
+    String oid        = toLowerCase(name) + "-oid";
+    String definition = "( " + oid + " NAME '" + name + "' SYNTAX " +
+                        syntax.getOID() + " )";
+
+    return new AttributeType(definition, name, Collections.singleton(name),
+                             oid, null, null, syntax,
                              AttributeUsage.USER_APPLICATIONS, false, false,
                              false, false);
   }
diff --git a/opends/src/server/org/opends/server/core/SchemaConfigManager.java b/opends/src/server/org/opends/server/core/SchemaConfigManager.java
index 81ceeaa..2a50f69 100644
--- a/opends/src/server/org/opends/server/core/SchemaConfigManager.java
+++ b/opends/src/server/org/opends/server/core/SchemaConfigManager.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.core;
 
@@ -1247,7 +1247,8 @@
             DITStructureRule dsr;
             try
             {
-              dsr = dsrSyntax.decodeDITStructureRule(v.getValue(), schema);
+              dsr = dsrSyntax.decodeDITStructureRule(v.getValue(), schema,
+                                                     false);
               dsr.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
               dsr.setSchemaFile(schemaFile);
             }
diff --git a/opends/src/server/org/opends/server/messages/BackendMessages.java b/opends/src/server/org/opends/server/messages/BackendMessages.java
index 2c1f81c..eec2341 100644
--- a/opends/src/server/org/opends/server/messages/BackendMessages.java
+++ b/opends/src/server/org/opends/server/messages/BackendMessages.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.messages;
 
@@ -2364,9 +2364,8 @@
 
   /**
    * The message ID for the message that will be used if an error occurs while
-   * trying to write an updated schema file.  This takes two arguments, which
-   * are the path to the schema file and a string representation of the
-   * exception that was caught.
+   * trying to write an updated schema file.  This takes a single argument,
+   * which is a string representation of the exception that was caught.
    */
   public static final int MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_SCHEMA =
        CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 222;
@@ -2374,6 +2373,595 @@
 
 
   /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to decode a new name form.  This takes two arguments, which
+   * are the name form string and a message explaining the problem that
+   * occurred.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 223;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to decode a new DIT content rule.  This takes two arguments,
+   * which are the DIT content rule string and a message explaining the problem
+   * that occurred.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DCR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 224;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to decode a new DIT structure rule.  This takes two arguments,
+   * which are the DIT structure rule string and a message explaining the
+   * problem that occurred.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DSR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 225;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * attempting to decode a new matching rule use.  This takes two arguments,
+   * which are the matching rule use string and a message explaining the problem
+   * that occurred.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 226;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * remove all values for a given attribute type.  This takes a single
+   * argument, which is the name of that attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DELETE_NO_VALUES =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 227;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new attribute type that conflicts with multiple existing attribute
+   * types.  This takes three arguments, which are the name or OID of the new
+   * attribute type, and the name or OID of the two attribute types that were
+   * found to conflict with the new type.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_ATTRTYPE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 228;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new attribute type that references an undefined superior attribute
+   * type.  This takes two arguments, which are the name or OID of the new
+   * attribute type and the name or OID of the superior attribute type.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_ATTRIBUTE_TYPE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 229;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new objectclas that conflicts with multiple existing objectclasses.
+   * This takes three arguments, which are the name or OID of the new
+   * objectclass, and the name or OID of the two objectclasses that were found
+   * to conflict with the new objectclass.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_OBJECTCLASS =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 230;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new name form that conflicts with multiple existing name forms.  This
+   * takes three arguments, which are the name or OID of the new name form, and
+   * the name or OID of the two name forms that were found to conflict with the
+   * new name form.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_NAME_FORM =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 231;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new name form that references an undefined structural objectclass.
+   * This takes two arguments, which are the name or OID of the new name form
+   * and the name or OID of the undefined objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_NF_UNDEFINED_STRUCTURAL_OC =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 232;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new name form that references an undefined required attribute type.
+   * This takes two arguments, which are the name or OID of the new name form
+   * and the name or OID of the undefined attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_NF_UNDEFINED_REQUIRED_ATTR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 233;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new name form that references an undefined optional attribute type.
+   * This takes two arguments, which are the name or OID of the new name form
+   * and the name or OID of the undefined attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_NF_UNDEFINED_OPTIONAL_ATTR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 234;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that conflicts with multiple existing DIT
+   * content rules.  This takes three arguments, which are the name of the new
+   * DIT content rule, and the names of the two DIT content rules that were
+   * found to conflict with the new DIT content rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DCR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 235;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that references a structural objectclass which
+   * is already referenced by an existing DIT copntent rule.  This takes three
+   * arguments, which are the name of the new DIT content rule, the name or OID
+   * of the structural objectclass, and the name of the conflicting DIT content
+   * rule.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_DCR =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 236;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that references a structural objectclass which
+   * is not defined in the server schema.  This takes two arguments, which are
+   * the name of the new DIT content rule and the name or OID of the undefined
+   * objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_STRUCTURAL_OC =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 237;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that references an auxiliary objectclass which
+   * is not defined in the server schema.  This takes two arguments, which are
+   * the name of the new DIT content rule and the name or OID of the undefined
+   * objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_AUXILIARY_OC =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 238;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that references a required attribute type
+   * which is not defined in the server schema.  This takes two arguments, which
+   * are the name of the new DIT content rule and the name or OID of the
+   * undefined attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_REQUIRED_ATTR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 239;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that references an optional attribute type
+   * which is not defined in the server schema.  This takes two arguments, which
+   * are the name of the new DIT content rule and the name or OID of the
+   * undefined attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_OPTIONAL_ATTR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 240;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that references a prohibited attribute type
+   * which is not defined in the server schema.  This takes two arguments, which
+   * are the name of the new DIT content rule and the name or OID of the
+   * undefined attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_PROHIBITED_ATTR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 241;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT structure rule that conflicts with multiple existing DIT
+   * structure rules.  This takes three arguments, which are the name or rule ID
+   * of the new DIT structure rule, and the names or rule IDs of the two DIT
+   * structure rules that were found to conflict with the new DIT structure
+   * rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DSR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 242;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT structure rule that references a name form that is already
+   * referenced by another DIT structure rule.  This takes three arguemnts,
+   * which are the name or rule ID of the new DIT structure rule, the name or
+   * OID of the name form, and the name or rule ID of the conflicting DIT
+   * structure rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_NAME_FORM_CONFLICT_FOR_ADD_DSR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 243;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT structure rule that references a name form that is not
+   * defined in the server schema.  This takes two arguments, which are the name
+   * or rule ID of the new DIT structure rule and the name or OID of the
+   * undefined name form.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DSR_UNDEFINED_NAME_FORM =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 244;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new matching rule use that conflicts with multiple existing matching
+   * rule uses.  This takes three arguments, which are the name of the new
+   * matching rule use, and the names of the two matching rule uses that were
+   * found to conflict with the new matching rule use.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_MR_USE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 245;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new matching rule use that references a matching rule that is already
+   * associated with another matching rule use.  This takes three arguments,
+   * which are the name of the new matching rule use, the name or OID of the
+   * matching rule, and the name of the conflicting matching rule use.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_MR_CONFLICT_FOR_ADD_MR_USE =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 246;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new matching rule use that references an attribute type that is not
+   * defined in the server schema.  This takes two arguments, which are the
+   * name of the new matching rule use and the name or OID of the undefined
+   * attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_MRU_UNDEFINED_ATTR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 247;
+
+
+
+  /**
+   * The message ID for the message that will be used if a circular reference
+   * is detected in the superior chain for an attribute type.  This takes a
+   * single argument, which is the name or OID of the attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_AT =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 248;
+
+
+
+  /**
+   * The message ID for the message that will be used if a circular reference
+   * is detected in the superior chain for an objectclass.  This takes a single
+   * argument, which is the name or OID of the objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_OC =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 249;
+
+
+
+  /**
+   * The message ID for the message that will be used if a circular reference
+   * is detected in the superior chain for a DIT structure rule.  This takes a
+   * single argument, which is the name or rule ID of the DIT structure rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_DSR =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 250;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to create copies of the current schema files, but the server was
+   * able to properly clean up after itself.  This takes a single argument,
+   * which is a string representation of the exception that was caught.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_CLEANED =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 251;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to create copies of the current schema files, and the server was
+   * not able to properly clean up after itself.  This takes a single argument,
+   * which is a string representation of the exception that was caught.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_NOT_CLEANED =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 252;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to write the new schema files, but the server was able to properly
+   * clean up after itself.  This takes a single argument,  which is a string
+   * representation of the exception that was caught.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_RESTORED =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 253;
+
+
+
+  /**
+   * The message ID for the message that will be used if an error occurs while
+   * trying to write the new schema files, and the server was not able to
+   * properly clean up after itself.  This takes a single argument, which is a
+   * string representation of the exception that was caught.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 254;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an attribute type from the schema fails because there is no such attribute
+   * type defined.  This takes a single argument, which is the name or OID of
+   * the attribute type to remove.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_ATTRIBUTE_TYPE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 255;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an attribute type from the schema fails because it is the superior type for
+   * another attribute type.  This takes two arguments, which are the name or
+   * OID of the attribute type to remove, and the name or OID of the subordinate
+   * attribute type.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_AT_SUPERIOR_TYPE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 256;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an attribute type from the schema fails because the attribute type is
+   * referenced by an objectclass.  This takes two arguments, which are the name
+   * or OID of the attribute type and the name or OID of the objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_OC =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 257;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an attribute type from the schema fails because the attribute type is
+   * referenced by a name form.  This takes two arguments, which are the name or
+   * OID of the attribute type and the name or OID of the name form.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_NF =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 258;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an attribute type from the schema fails because the attribute type is
+   * referenced by a DIT content rule.  This takes two arguments, which are the
+   * name or OID of the attribute type and the name of the DIT content rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_DCR =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 259;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an attribute type from the schema fails because the attribute type is
+   * referenced by a matching rule use.  This takes two arguments, which are the
+   * name or OID of the attribute type and the name of the matching rule use.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_MR_USE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 260;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an objectclass from the schema fails because there is no such objectclass
+   * defined.  This takes a single argument, which is the name or OID of the
+   * objectclass to remove.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_OBJECTCLASS =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 261;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an objectclass from the schema fails because it is the superior class for
+   * another objectclass.  This takes two arguments, which are the name or OID
+   * of the objectclass to remove, and the name or OID of the subordinate
+   * objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_OC_SUPERIOR_CLASS =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 262;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an objectclass from the schema fails because the objectclass is referenced
+   * by a name form.  This takes two arguments, which are the name or OID of the
+   * objectclass and the name or OID of the name form.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_OC_IN_NF =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 263;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * an objectclass from the schema fails because the objectclass is referenced
+   * by a DIT content rule.  This takes two arguments, which are the name or OID
+   * of the objectclass and the name of the DIT content rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_OC_IN_DCR =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 264;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * a name form from the schema fails because there is no such name form
+   * defined.  This takes a single argument, which is the name or OID of the
+   * name form to remove.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_NAME_FORM =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 265;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * a name form from the schema fails because the name form is referenced by a
+   * DIT structure rule.  This takes two arguments, which are the name or OID
+   * of the name form and the name or rule ID of the DIT structure rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_NF_IN_DSR =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 266;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * a DIT content rule from the schema fails because there is no such DIT
+   * content rule defined.  This takes a single argument, which is the name of
+   * the DIT content rule to remove.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_DCR =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 267;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * a DIT structure rule from the schema fails because there is no such DIT
+   * structure rule defined.  This takes a single argument, which is the name or
+   * rule ID of the DIT structure rule to remove.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_DSR =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 268;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * a DIT structure rule from the schema fails because it is the superior rule
+   * for another DIT structure rule.  This takes two arguments, which are the
+   * name or rule ID of the DIT structure rule to remove, and the name or rule
+   * ID of the subordinate rule.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_DSR_SUPERIOR_RULE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 269;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt to remove
+   * a matching rule use from the schema fails because there is no such matching
+   * rule use defined.  This takes a single argument, which is the name of the
+   * matching rule use to remove.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_MR_USE =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 270;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new name form that references an objectclass that is not structural.
+   * This takes two arguments, which are the name or OID of the new name form
+   * and the name or OID of the objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_NF_OC_NOT_STRUCTURAL =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 271;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new DIT content rule that references an objectclass that is not
+   * structural.  This takes two arguments, which are the name of the new DIT
+   * content rule and the name or OID of the objectclass.
+   */
+  public static final int MSGID_SCHEMA_MODIFY_DCR_OC_NOT_STRUCTURAL =
+       CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 272;
+
+
+
+  /**
+   * The message ID for the message that will be used if an attempt is made to
+   * add a new name form that references a structural objectclass which is
+   * already referenced by an existing name form.  This takes three arguments,
+   * which are the name or OID of the new name form, the name or OID of the
+   * structural objectclass, and the name or OID of the conflicting name form.
+   */
+  public static final int
+       MSGID_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_NF =
+            CATEGORY_MASK_BACKEND | SEVERITY_MASK_MILD_ERROR | 273;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -2609,12 +3197,9 @@
     registerMessage(MSGID_SCHEMA_DELETE_MODTYPE_NOT_SUPPORTED,
                     "The schema backend does not currently support removing " +
                     "existing schema elements.");
-    // FIXME -- Change the below message once we support removing schema
-    // elements.
     registerMessage(MSGID_SCHEMA_INVALID_MODIFICATION_TYPE,
                     "The schema backend does not support the %s modification " +
-                    "type.  It is currently only possible to add new schema " +
-                    "elements.");
+                    "type.");
     registerMessage(MSGID_SCHEMA_MODIFY_UNSUPPORTED_ATTRIBUTE_TYPE,
                     "The schema backend does not support the modification of " +
                     "the %s attribute type.  Only attribute types, object " +
@@ -2630,6 +3215,21 @@
     registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_DECODE_OBJECTCLASS,
                     "An error occurred while attempting to decode the object " +
                     "class \"%s\":  %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_DECODE_NAME_FORM,
+                    "An error occurred while attempting to decode the name " +
+                    "form \"%s\":  %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DCR,
+                    "An error occurred while attempting to decode the DIT " +
+                    "content rule \"%s\":  %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_DECODE_DSR,
+                    "An error occurred while attempting to decode the DIT " +
+                    "structure rule \"%s\":  %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_DECODE_MR_USE,
+                    "An error occurred while attempting to decode the " +
+                    "matching rule use \"%s\":  %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DELETE_NO_VALUES,
+                    "The server will not allow removing all values for the " +
+                    "%s attribute type in the server schema.");
     registerMessage(MSGID_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_OBJECTCLASS,
                     "Unable to add objectclass %s because its superior " +
                     "class of %s is not defined in the server schema.");
@@ -2648,7 +3248,7 @@
                     "of schema file %s:  %s.");
     registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_SCHEMA,
                     "An error occurred while attepting to write the updated " +
-                    "schema file %s:  %s.");
+                    "schema:  %s.");
     registerMessage(MSGID_SCHEMA_MODIFY_DN_NOT_SUPPORTED,
                     "Unwilling to rename entry \"%s\" because modify DN " +
                     "operations are not supported in the schema backend.");
@@ -2710,6 +3310,188 @@
                     "An error occurred while attempting to update the backup " +
                     "descriptor file %s with information about the schema " +
                     "backup:  %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_ATTRTYPE,
+                    "Unable to add attribute type %s because it conflicts " +
+                    "with multiple existing attribute types (%s and " +
+                    "%s).");
+    registerMessage(MSGID_SCHEMA_MODIFY_UNDEFINED_SUPERIOR_ATTRIBUTE_TYPE,
+                    "Unable to add attribute type %s because it references " +
+                    "superior attribute type %s which is not defined in the " +
+                    "server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_OBJECTCLASS,
+                    "Unable to add objectclass %s because it conflicts with " +
+                    "multiple existing objectclasses (%s and %s).");
+    registerMessage(MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_NAME_FORM,
+                    "Unable to add name form %s because it conflicts with " +
+                    "multiple existing name forms (%s and %s).");
+    registerMessage(MSGID_SCHEMA_MODIFY_NF_UNDEFINED_STRUCTURAL_OC,
+                    "Unable to add name form %s because it references " +
+                    "structural objectclass %s which is not defined in the " +
+                    "server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_NF_OC_NOT_STRUCTURAL,
+                    "Unable to add name form %s because it references " +
+                    "objectclass %s which is defined in the server schema " +
+                    "but is not a structural objectclass.");
+    registerMessage(MSGID_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_NF,
+                    "Unable to add name form %s because it references " +
+                    "structural objectclass %s which is already associated " +
+                    "with another name form %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_NF_UNDEFINED_REQUIRED_ATTR,
+                    "Unable to add name form %s because it references " +
+                    "required attribute type %s which is not defined in the " +
+                    "server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_NF_UNDEFINED_OPTIONAL_ATTR,
+                    "Unable to add name form %s because it references " +
+                    "optional attribute type %s which is not defined in the " +
+                    "server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DCR,
+                    "Unable to add DIT content rule %s because it conflicts " +
+                    "with multiple existing DIT content rules (%s and %s).");
+    registerMessage(MSGID_SCHEMA_MODIFY_STRUCTURAL_OC_CONFLICT_FOR_ADD_DCR,
+                    "Unable to add DIT content rule %s because it " +
+                    "references structural objectclass %s which is already " +
+                    "associated with another DIT content rule %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_STRUCTURAL_OC,
+                    "Unable to add DIT content rule %s because it " +
+                    "references structural objectclass %s which is not " +
+                    "defined in the server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DCR_OC_NOT_STRUCTURAL,
+                    "Unable to add DIT content rule %s because it " +
+                    "references structural objectclass %s which is defined " +
+                    "in the server schema but is not structural.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_AUXILIARY_OC,
+                    "Unable to add DIT content rule %s because it " +
+                    "references auxiliary objectclass %s which is not " +
+                    "defined in the server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_REQUIRED_ATTR,
+                    "Unable to add DIT content rule %s because it " +
+                    "references required attribute type %s which is not " +
+                    "defined in the server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_OPTIONAL_ATTR,
+                    "Unable to add DIT content rule %s because it " +
+                    "references optional attribute type %s which is not " +
+                    "defined in the server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DCR_UNDEFINED_PROHIBITED_ATTR,
+                    "Unable to add DIT content rule %s because it " +
+                    "references prohibited attribute type %s which is not " +
+                    "defined in the server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_DSR,
+                    "Unable to add DIT structure rule %s because it " +
+                    "conflicts with multiple existing DIT structure rules " +
+                    "(%s and %s).");
+    registerMessage(MSGID_SCHEMA_MODIFY_NAME_FORM_CONFLICT_FOR_ADD_DSR,
+                    "Unable to add DIT structure rule %s because it " +
+                    "references name form %s which is already associated " +
+                    "with another DIT structure rule %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_DSR_UNDEFINED_NAME_FORM,
+                    "Unable to add DIT structure rule %s because it " +
+                    "references name form %s which is not defined in the " +
+                    "server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_MULTIPLE_CONFLICTS_FOR_ADD_MR_USE,
+                    "Unable to add matching rule use %s because it " +
+                    "conflicts with multiple existing matching rule uses " +
+                    "(%s and %s).");
+    registerMessage(MSGID_SCHEMA_MODIFY_MR_CONFLICT_FOR_ADD_MR_USE,
+                    "Unable to add matching rule use %s because it " +
+                    "references matching rule %s which is already associated " +
+                    "with another matching rule use %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_MRU_UNDEFINED_ATTR,
+                    "Unable to add matching rule use %s because it " +
+                    "references attribute type %s which is not defined in " +
+                    "the server schema.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_AT,
+                    "Circular reference detected for attribute type %s in " +
+                    "which the superior type chain references the " +
+                    "attribute type itself.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_OC,
+                    "Circular reference detected for objectclass %s in which " +
+                    "the superior class chain references the objectclass " +
+                    "itself.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CIRCULAR_REFERENCE_DSR,
+                    "Circular reference detected for DIT structure rule %s " +
+                    "in which the superior rule chain references the DIT " +
+                    "structure rule itself.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_CLEANED,
+                    "An error occurred while attempting to create copies " +
+                    "of the existing schema files before applying the " +
+                    "updates:  %s.  The server was able to restore the " +
+                    "original schema configuration, so no additional " +
+                    "cleanup should be required.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_NOT_CLEANED,
+                    "An error occurred while attempting to create copies " +
+                    "of the existing schema files before applying the " +
+                    "updates:  %s.  A problem also occurred when attempting " +
+                    "to restore the original schema configuration, so the " +
+                    "server may be left in an inconsistent state and could " +
+                    "require manual cleanup.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_RESTORED,
+                    "An error occurred while attempting to write new " +
+                    "versions of the server schema files:  %s.   The server " +
+                    "was able to restore the original schema configuration, " +
+                    "so no additional cleanup should be required.");
+    registerMessage(MSGID_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED,
+                    "An error occrred while attempting to write new " +
+                    "versions of the server schema files:  %s.  A problem " +
+                    "also occured when attempting to restore the original " +
+                    "schema configuration, so the server may be left in an " +
+                    "inconsistent state and could require manual cleanup.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_ATTRIBUTE_TYPE,
+                    "Unable to remove attribute type %s from the server " +
+                    "schema because no such attribute type is defined.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_AT_SUPERIOR_TYPE,
+                    "Unable to remove attribute type %s from the server " +
+                    "schema because it is referenced as the superior type " +
+                    "for attribute type %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_OC,
+                    "Unable to remove attribute type %s from the server " +
+                    "schema because it is referenced as a required or " +
+                    "optional attribute type in objectclass %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_NF,
+                    "Unable to remove attribute type %s from the server " +
+                    "schema because it is referenced as a required or " +
+                    "optional attribute type in name form %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_DCR,
+                    "Unable to remove attribute type %s from the server " +
+                    "schema because it is referenced as a required, " +
+                    "optional, or prohibited attribute type in DIT content " +
+                    "rule %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_AT_IN_MR_USE,
+                    "Unable to remove attribute type %s from the server " +
+                    "schema because it is referenced by matching rule use %s");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_OBJECTCLASS,
+                    "Unable to remove objectclass %s from the server schema " +
+                    "because no such objectclass is defined.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_OC_SUPERIOR_CLASS,
+                    "Unable to remove objectclass %s from the server schema " +
+                    "because it is referenced as the superior class for " +
+                    "objectclass %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_OC_IN_NF,
+                    "Unable to remove objectclass %s from the server schema " +
+                    "because it is referenced as the structural class for " +
+                    "name form %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_OC_IN_DCR,
+                    "Unable to remove objectclass %s from the server schema " +
+                    "because it is referenced as a structural or auxiliary " +
+                    "class for DIT content rule %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_NAME_FORM,
+                    "Unable to remove name form %s from the server schema " +
+                    "because no such name form is defined.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NF_IN_DSR,
+                    "Unable to remove name form %s from the server schema " +
+                    "because it is referenced by DIT structure rule %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_DCR,
+                    "Unable to remove DIT content rule %s from the server " +
+                    "schema because no such DIT content rule is defined.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_DSR,
+                    "Unable to remove DIT structure rule %s from the server " +
+                    "schema because no such DIT structure rule is defined.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_DSR_SUPERIOR_RULE,
+                    "Unable to remove DIT structure rule %s from the server " +
+                    "schema because it is referenced as a superior rule for " +
+                    "DIT structure rule %s.");
+    registerMessage(MSGID_SCHEMA_MODIFY_REMOVE_NO_SUCH_MR_USE,
+                    "Unable to remove matching rule use %s from the server " +
+                    "schema because no such matching rule use is defined.");
 
 
     registerMessage(MSGID_SCHEMA_RESTORE_NO_SUCH_BACKUP,
diff --git a/opends/src/server/org/opends/server/messages/CoreMessages.java b/opends/src/server/org/opends/server/messages/CoreMessages.java
index 8d29a84..cdf526f 100644
--- a/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.messages;
 
@@ -6057,6 +6057,16 @@
        CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 578;
 
 
+  /**
+   * The message ID for the message that will be used if a circular reference is
+   * detected when attempting to rebuild schema element dependencies.  This
+   * takes a single argument, which is the definition string for the schema
+   * element that triggered the circular reference error.
+   */
+  public static final int MSGID_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE =
+       CATEGORY_MASK_CORE | SEVERITY_MASK_MILD_ERROR | 579;
+
+
 
   /**
    * Associates a set of generic messages with the message IDs defined
@@ -6751,6 +6761,11 @@
                     "Unable to register name form %s with the server schema " +
                     "because its name %s conflicts with the name for an " +
                     "existing name form %s.");
+    registerMessage(MSGID_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE,
+                    "Unable to update the schema element with definition " +
+                    "\"%s\" because a circular reference was identified " +
+                    "when attempting to rebuild other schema elements " +
+                    "dependent upon it.");
 
 
     registerMessage(MSGID_ADD_OP_INVALID_SYNTAX,
diff --git a/opends/src/server/org/opends/server/plugins/EntryUUIDPlugin.java b/opends/src/server/org/opends/server/plugins/EntryUUIDPlugin.java
index a8767cd..dffb210 100644
--- a/opends/src/server/org/opends/server/plugins/EntryUUIDPlugin.java
+++ b/opends/src/server/org/opends/server/plugins/EntryUUIDPlugin.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.plugins;
 
@@ -110,9 +110,15 @@
                                                         false);
     if (at == null)
     {
-      at = new AttributeType(ENTRYUUID, Collections.singleton(ENTRYUUID),
-                             ENTRYUUID, null, null,
-                             DirectoryConfig.getDefaultAttributeSyntax(),
+      String definition =
+           "( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' " +
+           "EQUALITY uuidMatch ORDERING uuidOrderingMatch " +
+           "SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE NO-USER-MODIFICATION " +
+           "USAGE directoryOperation X-ORIGIN 'RFC 4530' )";
+
+      at = new AttributeType(definition, ENTRYUUID,
+                             Collections.singleton(ENTRYUUID), ENTRYUUID, null,
+                             null, DirectoryConfig.getDefaultAttributeSyntax(),
                              AttributeUsage.DIRECTORY_OPERATION, false, true,
                              false, true);
     }
diff --git a/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java b/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java
index 8d6fa5c..d692564 100644
--- a/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java
+++ b/opends/src/server/org/opends/server/schema/AttributeTypeSyntax.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.schema;
 
@@ -926,12 +926,12 @@
     }
 
 
-    return new AttributeType(primaryName, typeNames, oid, description,
-                             superiorType, syntax, approximateMatchingRule,
-                             equalityMatchingRule, orderingMatchingRule,
-                             substringMatchingRule, attributeUsage,
-                             isCollective, isNoUserModification, isObsolete,
-                             isSingleValue, extraProperties);
+    return new AttributeType(value.stringValue(), primaryName, typeNames, oid,
+                             description, superiorType, syntax,
+                             approximateMatchingRule, equalityMatchingRule,
+                             orderingMatchingRule, substringMatchingRule,
+                             attributeUsage, isCollective, isNoUserModification,
+                             isObsolete, isSingleValue, extraProperties);
   }
 
 
diff --git a/opends/src/server/org/opends/server/schema/DITContentRuleSyntax.java b/opends/src/server/org/opends/server/schema/DITContentRuleSyntax.java
index 2fdf746..a8dc05e 100644
--- a/opends/src/server/org/opends/server/schema/DITContentRuleSyntax.java
+++ b/opends/src/server/org/opends/server/schema/DITContentRuleSyntax.java
@@ -22,16 +22,16 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.schema;
 
 
 
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.List;
 
 import org.opends.server.api.ApproximateMatchingRule;
 import org.opends.server.api.AttributeSyntax;
@@ -503,20 +503,19 @@
     // out what it is and how to treat what comes after it, then repeat until
     // we get to the end of the value.  But before we start, set default values
     // for everything else we might need to know.
-    ConcurrentHashMap<String,String> names =
-         new ConcurrentHashMap<String,String>();
+    LinkedHashMap<String,String> names = new LinkedHashMap<String,String>();
     String description = null;
     boolean isObsolete = false;
-    CopyOnWriteArraySet<ObjectClass> auxiliaryClasses =
-         new CopyOnWriteArraySet<ObjectClass>();
-    CopyOnWriteArraySet<AttributeType> requiredAttributes =
-         new CopyOnWriteArraySet<AttributeType>();
-    CopyOnWriteArraySet<AttributeType> optionalAttributes =
-         new CopyOnWriteArraySet<AttributeType>();
-    CopyOnWriteArraySet<AttributeType> prohibitedAttributes =
-         new CopyOnWriteArraySet<AttributeType>();
-    ConcurrentHashMap<String,CopyOnWriteArrayList<String>> extraProperties =
-         new ConcurrentHashMap<String,CopyOnWriteArrayList<String>>();
+    LinkedHashSet<ObjectClass> auxiliaryClasses =
+         new LinkedHashSet<ObjectClass>();
+    LinkedHashSet<AttributeType> requiredAttributes =
+         new LinkedHashSet<AttributeType>();
+    LinkedHashSet<AttributeType> optionalAttributes =
+         new LinkedHashSet<AttributeType>();
+    LinkedHashSet<AttributeType> prohibitedAttributes =
+         new LinkedHashSet<AttributeType>();
+    LinkedHashMap<String,List<String>> extraProperties =
+         new LinkedHashMap<String,List<String>>();
 
 
     while (true)
@@ -943,16 +942,15 @@
         // either a single value in single quotes or an open parenthesis
         // followed by one or more values in single quotes separated by spaces
         // followed by a close parenthesis.
-        CopyOnWriteArrayList<String> valueList =
-             new CopyOnWriteArrayList<String>();
+        LinkedList<String> valueList = new LinkedList<String>();
         pos = readExtraParameterValues(valueStr, valueList, pos);
         extraProperties.put(tokenName, valueList);
       }
     }
 
 
-    return new DITContentRule(structuralClass, names, description,
-                              auxiliaryClasses, requiredAttributes,
+    return new DITContentRule(value.stringValue(), structuralClass, names,
+                              description, auxiliaryClasses, requiredAttributes,
                               optionalAttributes, prohibitedAttributes,
                               isObsolete, extraProperties);
   }
@@ -1374,7 +1372,8 @@
    *                              the value.
    */
   private static int readExtraParameterValues(String valueStr,
-                          CopyOnWriteArrayList<String> valueList, int startPos)
+                                              List<String> valueList,
+                                              int startPos)
           throws DirectoryException
   {
     assert debugEnter(CLASS_NAME, "readExtraParameterValues",
diff --git a/opends/src/server/org/opends/server/schema/DITStructureRuleSyntax.java b/opends/src/server/org/opends/server/schema/DITStructureRuleSyntax.java
index de26f8e..9ebbfd6 100644
--- a/opends/src/server/org/opends/server/schema/DITStructureRuleSyntax.java
+++ b/opends/src/server/org/opends/server/schema/DITStructureRuleSyntax.java
@@ -22,16 +22,16 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.schema;
 
 
 
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.List;
 
 import org.opends.server.api.ApproximateMatchingRule;
 import org.opends.server.api.AttributeSyntax;
@@ -281,7 +281,7 @@
     // acceptable.
     try
     {
-      decodeDITStructureRule(value, DirectoryServer.getSchema());
+      decodeDITStructureRule(value, DirectoryServer.getSchema(), true);
       return true;
     }
     catch (DirectoryException de)
@@ -302,10 +302,16 @@
    * should not be in order to allow the desired capitalization to be
    * preserved).
    *
-   * @param  value   The ASN.1 octet string containing the value to decode (it
-   *                 does not need to be normalized).
-   * @param  schema  The schema to use to resolve references to other schema
-   *                 elements.
+   * @param  value                 The ASN.1 octet string containing the value
+   *                               to decode (it does not need to be
+   *                               normalized).
+   * @param  schema                The schema to use to resolve references to
+   *                               other schema elements.
+   * @param  allowUnknownElements  Indicates whether to allow values that
+   *                               reference a name form and/or superior rules
+   *                               which are not defined in the server schema.
+   *                               This should only be true when called by
+   *                               {@code valueIsAcceptable}.
    *
    * @return  The decoded DIT structure rule definition.
    *
@@ -313,7 +319,8 @@
    *                              DIT structure rule definition.
    */
   public static DITStructureRule decodeDITStructureRule(ByteString value,
-                                                        Schema schema)
+                                      Schema schema,
+                                      boolean allowUnknownElements)
          throws DirectoryException
   {
     assert debugEnter(CLASS_NAME, "decodeDITStructureRule",
@@ -428,14 +435,14 @@
     // out what it is and how to treat what comes after it, then repeat until
     // we get to the end of the value.  But before we start, set default values
     // for everything else we might need to know.
-    ConcurrentHashMap<String,String> names =
-         new ConcurrentHashMap<String,String>();
+    LinkedHashMap<String,String> names = new LinkedHashMap<String,String>();
     String description = null;
     boolean isObsolete = false;
     NameForm nameForm = null;
-    CopyOnWriteArraySet<DITStructureRule> superiorRules = null;
-    ConcurrentHashMap<String,CopyOnWriteArrayList<String>> extraProperties =
-         new ConcurrentHashMap<String,CopyOnWriteArrayList<String>>();
+    boolean nameFormGiven = false;
+    LinkedHashSet<DITStructureRule> superiorRules = null;
+    LinkedHashMap<String,List<String>> extraProperties =
+         new LinkedHashMap<String,List<String>>();
 
 
     while (true)
@@ -533,8 +540,9 @@
         StringBuilder woidBuffer = new StringBuilder();
         pos = readWOID(lowerStr, woidBuffer, pos);
 
+        nameFormGiven = true;
         nameForm = schema.getNameForm(woidBuffer.toString());
-        if (nameForm == null)
+        if ((nameForm == null) && (! allowUnknownElements))
         {
           int    msgID   = MSGID_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM;
           String message = getMessage(msgID, valueStr, woidBuffer.toString());
@@ -605,10 +613,14 @@
                  schema.getDITStructureRule(supRuleID);
             if (superiorRule == null)
             {
-              int msgID = MSGID_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID;
-              String message = getMessage(msgID, valueStr, supRuleID);
-              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                           message, msgID);
+              if (! allowUnknownElements)
+              {
+                int    msgID   = MSGID_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID;
+                String message = getMessage(msgID, valueStr, supRuleID);
+                throw new DirectoryException(
+                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message,
+                               msgID);
+              }
             }
             else
             {
@@ -698,10 +710,13 @@
           DITStructureRule superiorRule = schema.getDITStructureRule(supRuleID);
           if (superiorRule == null)
           {
-            int msgID = MSGID_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID;
-            String message = getMessage(msgID, valueStr, supRuleID);
-            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
-                                         message, msgID);
+            if (! allowUnknownElements)
+            {
+              int    msgID   = MSGID_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID;
+              String message = getMessage(msgID, valueStr, supRuleID);
+              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
+                                           message, msgID);
+            }
           }
           else
           {
@@ -724,7 +739,7 @@
           }
         }
 
-        superiorRules = new CopyOnWriteArraySet<DITStructureRule>(superiorList);
+        superiorRules = new LinkedHashSet<DITStructureRule>(superiorList);
       }
       else
       {
@@ -732,15 +747,15 @@
         // either a single value in single quotes or an open parenthesis
         // followed by one or more values in single quotes separated by spaces
         // followed by a close parenthesis.
-        CopyOnWriteArrayList<String> valueList =
-             new CopyOnWriteArrayList<String>();
+        LinkedList<String> valueList =
+             new LinkedList<String>();
         pos = readExtraParameterValues(valueStr, valueList, pos);
         extraProperties.put(tokenName, valueList);
       }
     }
 
 
-    if (nameForm == null)
+    if ((nameForm == null) && (! nameFormGiven))
     {
       int    msgID   = MSGID_ATTR_SYNTAX_DSR_NO_NAME_FORM;
       String message = getMessage(msgID, valueStr);
@@ -749,8 +764,9 @@
     }
 
 
-    return new DITStructureRule(names, ruleID, description, isObsolete,
-                                nameForm, superiorRules, extraProperties);
+    return new DITStructureRule(value.stringValue(), names, ruleID, description,
+                                isObsolete, nameForm, superiorRules,
+                                extraProperties);
   }
 
 
@@ -1170,7 +1186,7 @@
    *                              the value.
    */
   private static int readExtraParameterValues(String valueStr,
-                          CopyOnWriteArrayList<String> valueList, int startPos)
+                          List<String> valueList, int startPos)
           throws DirectoryException
   {
     assert debugEnter(CLASS_NAME, "readExtraParameterValues",
diff --git a/opends/src/server/org/opends/server/schema/MatchingRuleUseSyntax.java b/opends/src/server/org/opends/server/schema/MatchingRuleUseSyntax.java
index f1bcc1a..e238546 100644
--- a/opends/src/server/org/opends/server/schema/MatchingRuleUseSyntax.java
+++ b/opends/src/server/org/opends/server/schema/MatchingRuleUseSyntax.java
@@ -22,14 +22,16 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.schema;
 
 
 
-import java.util.*;
-import java.util.concurrent.*;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
 
 import org.opends.server.api.ApproximateMatchingRule;
 import org.opends.server.api.AttributeSyntax;
@@ -490,13 +492,12 @@
     // out what it is and how to treat what comes after it, then repeat until
     // we get to the end of the value.  But before we start, set default values
     // for everything else we might need to know.
-    ConcurrentHashMap<String,String> names =
-         new ConcurrentHashMap<String,String>();
+    LinkedHashMap<String,String> names = new LinkedHashMap<String,String>();
     String description = null;
     boolean isObsolete = false;
-    CopyOnWriteArraySet<AttributeType> attributes = null;
-    ConcurrentHashMap<String,CopyOnWriteArrayList<String>> extraProperties =
-         new ConcurrentHashMap<String,CopyOnWriteArrayList<String>>();
+    LinkedHashSet<AttributeType> attributes = null;
+    LinkedHashMap<String,List<String>> extraProperties =
+         new LinkedHashMap<String,List<String>>();
 
     while (true)
     {
@@ -664,7 +665,7 @@
           attrs.add(attr);
         }
 
-        attributes = new CopyOnWriteArraySet<AttributeType>(attrs);
+        attributes = new LinkedHashSet<AttributeType>(attrs);
       }
       else
       {
@@ -672,8 +673,7 @@
         // either a single value in single quotes or an open parenthesis
         // followed by one or more values in single quotes separated by spaces
         // followed by a close parenthesis.
-        CopyOnWriteArrayList<String> valueList =
-             new CopyOnWriteArrayList<String>();
+        LinkedList<String> valueList = new LinkedList<String>();
         pos = readExtraParameterValues(valueStr, valueList, pos);
         extraProperties.put(tokenName, valueList);
       }
@@ -690,8 +690,9 @@
     }
 
 
-    return new MatchingRuleUse(matchingRule, names, description, isObsolete,
-                               attributes, extraProperties);
+    return new MatchingRuleUse(value.stringValue(), matchingRule, names,
+                               description, isObsolete, attributes,
+                               extraProperties);
   }
 
 
@@ -1110,7 +1111,7 @@
    *                              the value.
    */
   private static int readExtraParameterValues(String valueStr,
-                          CopyOnWriteArrayList<String> valueList, int startPos)
+                          List<String> valueList, int startPos)
           throws DirectoryException
   {
     assert debugEnter(CLASS_NAME, "readExtraParameterValues",
diff --git a/opends/src/server/org/opends/server/schema/NameFormSyntax.java b/opends/src/server/org/opends/server/schema/NameFormSyntax.java
index 1624a4e..c349dc6 100644
--- a/opends/src/server/org/opends/server/schema/NameFormSyntax.java
+++ b/opends/src/server/org/opends/server/schema/NameFormSyntax.java
@@ -22,16 +22,16 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.schema;
 
 
 
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.List;
 
 import org.opends.server.api.ApproximateMatchingRule;
 import org.opends.server.api.AttributeSyntax;
@@ -478,17 +478,16 @@
     // out what it is and how to treat what comes after it, then repeat until
     // we get to the end of the value.  But before we start, set default values
     // for everything else we might need to know.
-    ConcurrentHashMap<String,String> names =
-         new ConcurrentHashMap<String,String>();
+    LinkedHashMap<String,String> names = new LinkedHashMap<String,String>();
     String description = null;
     boolean isObsolete = false;
     ObjectClass structuralClass = null;
-    CopyOnWriteArraySet<AttributeType> requiredAttributes =
-         new CopyOnWriteArraySet<AttributeType>();
-    CopyOnWriteArraySet<AttributeType> optionalAttributes =
-         new CopyOnWriteArraySet<AttributeType>();
-    ConcurrentHashMap<String,CopyOnWriteArrayList<String>> extraProperties =
-         new ConcurrentHashMap<String,CopyOnWriteArrayList<String>>();
+    LinkedHashSet<AttributeType> requiredAttributes =
+         new LinkedHashSet<AttributeType>();
+    LinkedHashSet<AttributeType> optionalAttributes =
+         new LinkedHashSet<AttributeType>();
+    LinkedHashMap<String,List<String>> extraProperties =
+         new LinkedHashMap<String,List<String>>();
 
 
     while (true)
@@ -775,8 +774,7 @@
         // either a single value in single quotes or an open parenthesis
         // followed by one or more values in single quotes separated by spaces
         // followed by a close parenthesis.
-        CopyOnWriteArrayList<String> valueList =
-             new CopyOnWriteArrayList<String>();
+        LinkedList<String> valueList = new LinkedList<String>();
         pos = readExtraParameterValues(valueStr, valueList, pos);
         extraProperties.put(tokenName, valueList);
       }
@@ -794,9 +792,9 @@
     }
 
 
-    return new NameForm(names, oid, description, isObsolete, structuralClass,
-                        requiredAttributes, optionalAttributes,
-                        extraProperties);
+    return new NameForm(value.stringValue(), names, oid, description,
+                        isObsolete, structuralClass, requiredAttributes,
+                        optionalAttributes, extraProperties);
   }
 
 
@@ -1215,7 +1213,7 @@
    *                              the value.
    */
   private static int readExtraParameterValues(String valueStr,
-                          CopyOnWriteArrayList<String> valueList, int startPos)
+                          List<String> valueList, int startPos)
           throws DirectoryException
   {
     assert debugEnter(CLASS_NAME, "readExtraParameterValues",
diff --git a/opends/src/server/org/opends/server/schema/ObjectClassSyntax.java b/opends/src/server/org/opends/server/schema/ObjectClassSyntax.java
index d6fd0d5..627ccfb 100644
--- a/opends/src/server/org/opends/server/schema/ObjectClassSyntax.java
+++ b/opends/src/server/org/opends/server/schema/ObjectClassSyntax.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.schema;
 
@@ -807,9 +807,10 @@
     }
 
 
-    return new ObjectClass(primaryName, names, oid, description, superiorClass,
-                           requiredAttributes, optionalAttributes,
-                           objectClassType, isObsolete, extraProperties);
+    return new ObjectClass(value.stringValue(), primaryName, names, oid,
+                           description, superiorClass, requiredAttributes,
+                           optionalAttributes, objectClassType, isObsolete,
+                           extraProperties);
   }
 
 
diff --git a/opends/src/server/org/opends/server/types/AttributeType.java b/opends/src/server/org/opends/server/types/AttributeType.java
index f35f8aa..d87e727 100644
--- a/opends/src/server/org/opends/server/types/AttributeType.java
+++ b/opends/src/server/org/opends/server/types/AttributeType.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
@@ -31,17 +31,20 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+
 import org.opends.server.api.ApproximateMatchingRule;
 import org.opends.server.api.AttributeSyntax;
 import org.opends.server.api.EqualityMatchingRule;
 import org.opends.server.api.OrderingMatchingRule;
 import org.opends.server.api.SubstringMatchingRule;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.schema.AttributeTypeSyntax;
 
 import static org.opends.server.loggers.Debug.*;
 import static org.opends.server.messages.CoreMessages.*;
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.Validator.*;
 
 
 
@@ -60,7 +63,9 @@
  * ordering will be preserved when the associated fields are accessed
  * via their getters or via the {@link #toString()} methods.
  */
-public final class AttributeType extends CommonSchemaElements
+public final class AttributeType
+       extends CommonSchemaElements
+       implements SchemaFileElement
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -102,6 +107,9 @@
   // The ordering matching rule for this attribute type.
   private final OrderingMatchingRule orderingMatchingRule;
 
+  // The definition string used to create this attribute type.
+  private final String definition;
+
   // The substring matching rule for this attribute type.
   private final SubstringMatchingRule substringMatchingRule;
 
@@ -115,6 +123,9 @@
    * from the set of <code>names</code> will be used as the primary
    * name.
    *
+   * @param definition
+   *          The definition string used to create this attribute
+   *          type.  It must not be {@code null}.
    * @param primaryName
    *          The primary name for this attribute type, or
    *          <code>null</code> if there is no primary name.
@@ -122,8 +133,8 @@
    *          The full set of names for this attribute type, or
    *          <code>null</code> if there are no names.
    * @param oid
-   *          The OID for this attribute type (must not be
-   *          <code>null</code>).
+   *          The OID for this attribute type.  It must not be
+   *          {@code null}.
    * @param description
    *          The description for the attribute type, or
    *          <code>null</code> if there is no description.
@@ -150,7 +161,7 @@
    *          Indicates whether this attribute type is declared
    *          "single-value".
    */
-  public AttributeType(String primaryName,
+  public AttributeType(String definition, String primaryName,
                        Collection<String> typeNames,
                        String oid, String description,
                        AttributeType superiorType,
@@ -160,7 +171,7 @@
                        boolean isNoUserModification,
                        boolean isObsolete, boolean isSingleValue)
   {
-    this(primaryName, typeNames, oid, description,
+    this(definition, primaryName, typeNames, oid, description,
         superiorType, syntax, null, null, null,
         null, attributeUsage, isCollective,
         isNoUserModification, isObsolete, isSingleValue, null);
@@ -176,6 +187,9 @@
    * from the set of <code>names</code> will be used as the primary
    * name.
    *
+   * @param definition
+   *          The definition string used to create this attribute
+   *          type.  It must not be {@code null}.
    * @param primaryName
    *          The primary name for this attribute type, or
    *          <code>null</code> if there is no primary name.
@@ -183,8 +197,8 @@
    *          The full set of names for this attribute type, or
    *          <code>null</code> if there are no names.
    * @param oid
-   *          The OID for this attribute type (must not be
-   *          <code>null</code>).
+   *          The OID for this attribute type.  It must not be
+   *          {@code null}.
    * @param description
    *          The description for the attribute type, or
    *          <code>null</code> if there is no description.
@@ -225,10 +239,8 @@
    * @param extraProperties
    *          A set of extra properties for this attribute type, or
    *          <code>null</code> if there are no extra properties.
-   * @throws NullPointerException
-   *           If the provided OID was <code>null</code>.
    */
-  public AttributeType(String primaryName,
+  public AttributeType(String definition, String primaryName,
                        Collection<String> typeNames,
                        String oid, String description,
                        AttributeType superiorType,
@@ -243,12 +255,13 @@
                        boolean isNoUserModification,
                        boolean isObsolete, boolean isSingleValue,
                        Map<String,List<String>> extraProperties)
-                       throws NullPointerException
   {
     super(primaryName, typeNames, oid, description, isObsolete,
         extraProperties);
 
-    assert debugConstructor(CLASS_NAME,String.valueOf(primaryName),
+    assert debugConstructor(CLASS_NAME,
+                              String.valueOf(definition),
+                              String.valueOf(primaryName),
                               String.valueOf(typeNames),
                               String.valueOf(oid),
                               String.valueOf(description),
@@ -265,6 +278,9 @@
                               String.valueOf(isSingleValue),
                               String.valueOf(extraProperties));
 
+    ensureNotNull(definition, oid);
+
+    this.definition   = definition;
     this.superiorType = superiorType;
     this.isCollective = isCollective;
     this.isNoUserModification = isNoUserModification;
@@ -352,6 +368,51 @@
 
 
   /**
+   * Retrieves the definition string used to create this attribute
+   * type.
+   *
+   * @return  The definition string used to create this attribute
+   *          type.
+   */
+  public String getDefinition()
+  {
+    assert debugEnter(CLASS_NAME, "getDefinition");
+
+    return definition;
+  }
+
+
+
+  /**
+   * Creates a new instance of this attribute type based on the
+   * definition string.  It will also preserve other state information
+   * associated with this attribute type that is not included in the
+   * definition string (e.g., the name of the schema file with which
+   * it is associated).
+   *
+   * @return  The new instance of this attribute type based on the
+   *          definition string.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to create a new attribute type
+   *                              instance from the definition string.
+   */
+  public AttributeType recreateFromDefinition()
+         throws DirectoryException
+  {
+    ByteString value  = ByteStringFactory.create(definition);
+    Schema     schema = DirectoryServer.getSchema();
+
+    AttributeType at =
+         AttributeTypeSyntax.decodeAttributeType(value, schema);
+    at.setSchemaFile(getSchemaFile());
+
+    return at;
+  }
+
+
+
+  /**
    * Retrieves the superior type for this attribute type.
    *
    * @return  The superior type for this attribute type, or
diff --git a/opends/src/server/org/opends/server/types/CommonSchemaElements.java b/opends/src/server/org/opends/server/types/CommonSchemaElements.java
index 8304121..d91916d 100644
--- a/opends/src/server/org/opends/server/types/CommonSchemaElements.java
+++ b/opends/src/server/org/opends/server/types/CommonSchemaElements.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
@@ -32,10 +32,12 @@
 import static org.opends.server.loggers.Debug.debugEnter;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.toLowerCase;
+import static org.opends.server.util.Validator.*;
 
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -59,6 +61,12 @@
  * Where ordered sets of names, or extra properties are provided, the
  * ordering will be preserved when the associated fields are accessed
  * via their getters or via the {@link #toString()} methods.
+ * <p>
+ * Note that these schema elements are not completely immutable, as
+ * the set of extra properties for the schema element may be altered
+ * after the element is created.  Among other things, this allows the
+ * associated schema file to be edited so that an element created over
+ * protocol may be associated with a particular schema file.
  */
 public abstract class CommonSchemaElements {
   /**
@@ -316,10 +324,10 @@
 
 
   /**
-   * Retrieves the path to the schema file that contains the
+   * Retrieves the name of the schema file that contains the
    * definition for this schema definition.
    *
-   * @return The path to the schema file that contains the definition
+   * @return The name of the schema file that contains the definition
    *         for this schema definition, or <code>null</code> if it
    *         is not known or if it is not stored in any schema file.
    */
@@ -338,6 +346,25 @@
 
 
   /**
+   * Specifies the name of the schema file that contains the
+   * definition for this schema element.  If a schema file is already
+   * defined in the set of extra properties, then it will be
+   * overwritten.  If the provided schema file value is {@code null},
+   * then any existing schema file definition will be removed.
+   *
+   * @param  schemaFile  The name of the schema file that contains the
+   *                     definition for this schema element.
+   */
+  public final void setSchemaFile(String schemaFile) {
+    assert debugEnter(CLASS_NAME, "setSchemaFile",
+                      String.valueOf(schemaFile));
+
+    setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
+  }
+
+
+
+  /**
    * Retrieves the description for this schema definition.
    *
    * @return The description for this schema definition, or
@@ -401,6 +428,72 @@
 
 
   /**
+   * Sets the value for an "extra" property for this schema element.
+   * If a property already exists with the specified name, then it
+   * will be overwritten.  If the value is {@code null}, then any
+   * existing property with the given name will be removed.
+   *
+   * @param  name   The name for the "extra" property.  It must not be
+   *                {@code null}.
+   * @param  value  The value for the "extra" property.  If it is
+   *                {@code null}, then any existing definition will be
+   *                removed.
+   */
+  public final void setExtraProperty(String name, String value) {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(value));
+
+    ensureNotNull(name);
+
+    if (value == null)
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> values = new LinkedList<String>();
+      values.add(value);
+
+      extraProperties.put(name, values);
+    }
+  }
+
+
+
+  /**
+   * Sets the values for an "extra" property for this schema element.
+   * If a property already exists with the specified name, then it
+   * will be overwritten.  If the set of values is {@code null} or
+   * empty, then any existing property with the given name will be
+   * removed.
+   *
+   * @param  name    The name for the "extra" property.  It must not
+   *                 be {@code null}.
+   * @param  values  The set of values for the "extra" property.  If
+   *                 it is {@code null} or empty, then any existing
+   *                 definition will be removed.
+   */
+  public final void setExtraProperty(String name,
+                                     List<String> values) {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(values));
+
+    ensureNotNull(name);
+
+    if ((values == null) || values.isEmpty())
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> valuesCopy = new LinkedList<String>(values);
+      extraProperties.put(name, valuesCopy);
+    }
+  }
+
+
+
+  /**
    * Indicates whether the provided object is equal to this attribute
    * type. The object will be considered equal if it is an attribute
    * type with the same OID as the current type.
diff --git a/opends/src/server/org/opends/server/types/DITContentRule.java b/opends/src/server/org/opends/server/types/DITContentRule.java
index 70d11aa..088a9b4 100644
--- a/opends/src/server/org/opends/server/types/DITContentRule.java
+++ b/opends/src/server/org/opends/server/types/DITContentRule.java
@@ -22,16 +22,21 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
 
 
 import java.util.Iterator;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opends.server.schema.DITContentRuleSyntax;
 
 import static org.opends.server.loggers.Debug.*;
 import static org.opends.server.loggers.Error.*;
@@ -39,6 +44,7 @@
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
+import static org.opends.server.util.Validator.*;
 
 
 
@@ -48,7 +54,8 @@
  * given structural objectclass, and also indicates which auxiliary
  * classes that may be included in the entry.
  */
-public class DITContentRule
+public final class DITContentRule
+       implements SchemaFileElement
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -59,40 +66,38 @@
 
 
   // Indicates whether this content rule is declared "obsolete".
-  private boolean isObsolete;
+  private final boolean isObsolete;
 
   // The set of additional name-value pairs associated with this
   // content rule definition.
-  private ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-               extraProperties;
+  private final Map<String,List<String>> extraProperties;
 
   // The set of names for this DIT content rule, in a mapping between
   // the all-lowercase form and the user-defined form.
-  private ConcurrentHashMap<String,String> names;
+  private final Map<String,String> names;
+
+  // The structural objectclass for this DIT content rule.
+  private final ObjectClass structuralClass;
 
   // The set of auxiliary objectclasses that entries with this content
   // rule may contain, in a mapping between the objectclass and the
   // user-defined name for that class.
-  private CopyOnWriteArraySet<ObjectClass> auxiliaryClasses;
+  private final Set<ObjectClass> auxiliaryClasses;
 
   // The set of optional attribute types for this DIT content rule.
-  private CopyOnWriteArraySet<AttributeType> optionalAttributes;
+  private final Set<AttributeType> optionalAttributes;
 
   // The set of prohibited attribute types for this DIT content rule.
-  private CopyOnWriteArraySet<AttributeType> prohibitedAttributes;
+  private final Set<AttributeType> prohibitedAttributes;
 
   // The set of required attribute types for this DIT content rule.
-  private CopyOnWriteArraySet<AttributeType> requiredAttributes;
+  private final Set<AttributeType> requiredAttributes;
 
-  // The structural objectclass for this DIT content rule.
-  private ObjectClass structuralClass;
+  // The definition string used to create this DIT content rule.
+  private final String definition;
 
-  // The description for this attribute type.
-  private String description;
-
-  // The path to the schema file that contains this DIT content rule
-  // definition.
-  private String schemaFile;
+  // The description for this DIT content rule.
+  private final String description;
 
 
 
@@ -100,8 +105,12 @@
    * Creates a new DIT content rule definition with the provided
    * information.
    *
+   * @param  definition            The definition string used to
+   *                               create this DIT content rule.  It
+   *                               must not be {@code null}.
    * @param  structuralClass       The structural objectclass for this
-   *                               DIT content rule.
+   *                               DIT content rule.  It must not be
+   *                               {@code null}.
    * @param  names                 The set of names that may be used
    *                               to reference this DIT content rule.
    * @param  description           The description for this DIT
@@ -119,41 +128,139 @@
    * @param  extraProperties       A set of extra properties for this
    *                               DIT content rule.
    */
-  public DITContentRule(ObjectClass structuralClass,
-              ConcurrentHashMap<String,String> names,
-              String description,
-              CopyOnWriteArraySet<ObjectClass> auxiliaryClasses,
-              CopyOnWriteArraySet<AttributeType> requiredAttributes,
-              CopyOnWriteArraySet<AttributeType> optionalAttributes,
-              CopyOnWriteArraySet<AttributeType> prohibitedAttributes,
-              boolean isObsolete,
-              ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-                   extraProperties)
+  public DITContentRule(String definition,
+                        ObjectClass structuralClass,
+                        Map<String,String> names, String description,
+                        Set<ObjectClass> auxiliaryClasses,
+                        Set<AttributeType> requiredAttributes,
+                        Set<AttributeType> optionalAttributes,
+                        Set<AttributeType> prohibitedAttributes,
+                        boolean isObsolete,
+                        Map<String,List<String>> extraProperties)
   {
-    assert debugConstructor(CLASS_NAME,
-                            new String[]
-                            {
-                              String.valueOf(structuralClass),
-                              String.valueOf(names),
-                              String.valueOf(description),
-                              String.valueOf(auxiliaryClasses),
-                              String.valueOf(requiredAttributes),
-                              String.valueOf(optionalAttributes),
-                              String.valueOf(prohibitedAttributes),
-                              String.valueOf(isObsolete),
-                              String.valueOf(extraProperties)
-                            });
+    assert debugConstructor(CLASS_NAME, String.valueOf(definition),
+                            String.valueOf(structuralClass),
+                            String.valueOf(names),
+                            String.valueOf(description),
+                            String.valueOf(auxiliaryClasses),
+                            String.valueOf(requiredAttributes),
+                            String.valueOf(optionalAttributes),
+                            String.valueOf(prohibitedAttributes),
+                            String.valueOf(isObsolete),
+                            String.valueOf(extraProperties));
 
-    this.structuralClass      = structuralClass;
-    this.names                = names;
-    this.description          = description;
-    this.auxiliaryClasses     = auxiliaryClasses;
-    this.requiredAttributes   = requiredAttributes;
-    this.optionalAttributes   = optionalAttributes;
-    this.prohibitedAttributes = prohibitedAttributes;
-    this.isObsolete           = isObsolete;
-    this.schemaFile           = null;
-    this.extraProperties      = extraProperties;
+    ensureNotNull(definition, structuralClass);
+
+    this.definition      = definition;
+    this.structuralClass = structuralClass;
+    this.description     = description;
+    this.isObsolete      = isObsolete;
+
+    if ((names == null) || names.isEmpty())
+    {
+      this.names = new LinkedHashMap<String,String>(0);
+    }
+    else
+    {
+      this.names = new LinkedHashMap<String,String>(names);
+    }
+
+    if ((auxiliaryClasses == null) || auxiliaryClasses.isEmpty())
+    {
+      this.auxiliaryClasses = new LinkedHashSet<ObjectClass>(0);
+    }
+    else
+    {
+      this.auxiliaryClasses =
+           new LinkedHashSet<ObjectClass>(auxiliaryClasses);
+    }
+
+    if ((requiredAttributes == null) || requiredAttributes.isEmpty())
+    {
+      this.requiredAttributes = new LinkedHashSet<AttributeType>(0);
+    }
+    else
+    {
+      this.requiredAttributes =
+           new LinkedHashSet<AttributeType>(requiredAttributes);
+    }
+
+    if ((optionalAttributes == null) || optionalAttributes.isEmpty())
+    {
+      this.optionalAttributes = new LinkedHashSet<AttributeType>(0);
+    }
+    else
+    {
+      this.optionalAttributes =
+           new LinkedHashSet<AttributeType>(optionalAttributes);
+    }
+
+    if ((prohibitedAttributes == null) ||
+        prohibitedAttributes.isEmpty())
+    {
+      this.prohibitedAttributes = new LinkedHashSet<AttributeType>(0);
+    }
+    else
+    {
+      this.prohibitedAttributes =
+           new LinkedHashSet<AttributeType>(prohibitedAttributes);
+    }
+
+    if ((extraProperties == null) || extraProperties.isEmpty())
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(0);
+    }
+    else
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(extraProperties);
+    }
+  }
+
+
+
+  /**
+   * Retrieves the definition string used to create this DIT content
+   * rule.
+   *
+   * @return  The definition string used to create this DIT content
+   *          rule.
+   */
+  public String getDefinition()
+  {
+    assert debugEnter(CLASS_NAME, "getDefinition");
+
+    return definition;
+  }
+
+
+
+  /**
+   * Creates a new instance of this DIT content rule based on the
+   * definition string.  It will also preserve other state information
+   * associated with this DIT content rule that is not included in the
+   * definition string (e.g., the name of the schema file with which
+   * it is associated).
+   *
+   * @return  The new instance of this DIT content rule based on the
+   *          definition string.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to create a new DIT content rule
+   *                              instance from the definition string.
+   */
+  public DITContentRule recreateFromDefinition()
+         throws DirectoryException
+  {
+    ByteString value  = ByteStringFactory.create(definition);
+    Schema     schema = DirectoryConfig.getSchema();
+
+    DITContentRule dcr =
+         DITContentRuleSyntax.decodeDITContentRule(value, schema);
+    dcr.setSchemaFile(getSchemaFile());
+
+    return dcr;
   }
 
 
@@ -173,22 +280,6 @@
 
 
   /**
-   * Specifies the structural objectclass for this DIT content rule.
-   *
-   * @param  structuralClass  The structural objectclass for this DIT
-   *                          content rule.
-   */
-  public void setStructuralClass(ObjectClass structuralClass)
-  {
-    assert debugEnter(CLASS_NAME, "setStructuralClass",
-                      String.valueOf(structuralClass));
-
-    this.structuralClass = structuralClass;
-  }
-
-
-
-  /**
    * Retrieves the set of names that may be used to reference this DIT
    * content rule.  The returned object will be a mapping between each
    * name in all lowercase characters and that name in a user-defined
@@ -197,7 +288,7 @@
    * @return  The set of names that may be used to reference this DIT
    *          content rule.
    */
-  public ConcurrentHashMap<String,String> getNames()
+  public Map<String,String> getNames()
   {
     assert debugEnter(CLASS_NAME, "getNames");
 
@@ -211,7 +302,7 @@
    * rule.
    *
    * @return  The primary name to use to reference this DIT content
-   *          rule, or <CODE>null</CODE> if there is none.
+   *          rule, or {@code null} if there is none.
    */
   public String getName()
   {
@@ -230,33 +321,15 @@
 
 
   /**
-   * Specifies the set of names that may be used to reference this DIT
-   * content rule.  The provided set must provide a mapping between
-   * each name in all lowercase characters and that name in a
-   * user-defined form (which may include mixed capitalization).
-   *
-   * @param  names  The set of names that may be used to reference
-   *                this DIT content rule.
-   */
-  public void setNames(ConcurrentHashMap<String,String> names)
-  {
-    assert debugEnter(CLASS_NAME, "setNames", String.valueOf(names));
-
-    this.names = names;
-  }
-
-
-
-  /**
    * Indicates whether the provided lowercase name may be used to
    * reference this DIT content rule.
    *
    * @param  lowerName  The name for which to make the determination,
    *                    in all lowercase characters.
    *
-   * @return  <CODE>true</CODE> if the provided lowercase name may be
-   *          used to reference this DIT content rule, or
-   *          <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided lowercase name may be used
+   *          to reference this DIT content rule, or {@code false} if
+   *          not.
    */
   public boolean hasName(String lowerName)
   {
@@ -269,62 +342,34 @@
 
 
   /**
-   * Adds the provided name to the set of names that may be used to
-   * reference this DIT content rule.
-   *
-   * @param  name  The name to add to the set of names that may be
-   *               used to reference this DIT content rule.
-   */
-  public void addName(String name)
-  {
-    assert debugEnter(CLASS_NAME, "addName", String.valueOf(name));
-
-    String lowerName = toLowerCase(name);
-    names.put(lowerName, name);
-  }
-
-
-
-  /**
-   * Removes the provided lowercase name from the set of names that
-   * may be used to reference this DIT content rule.
-   *
-   * @param  lowerName  The name to remove from the set of names that
-   *                    may be used to reference this DIT content
-   *                    rule, in all lowercase characters.
-   */
-  public void removeName(String lowerName)
-  {
-    assert debugEnter(CLASS_NAME, "removeName",
-                      String.valueOf(lowerName));
-
-    names.remove(lowerName);
-  }
-
-
-
-  /**
-   * Retrieves the path to the schema file that contains the
+   * Retrieves the name of the schema file that contains the
    * definition for this DIT content rule.
    *
-   * @return  The path to the schema file that contains the definition
-   *          for this DIT content rule, or <CODE>null</CODE> if it is
-   *          not known or if it is not stored in any schema file.
+   * @return  The name of the schema file that contains the definition
+   *          for this DIT content rule, or {@code null} if it is not
+   *          known or if it is not stored in any schema file.
    */
   public String getSchemaFile()
   {
     assert debugEnter(CLASS_NAME, "getSchemaFile");
 
-    return schemaFile;
+    List<String> values =
+         extraProperties.get(SCHEMA_PROPERTY_FILENAME);
+    if ((values == null) || values.isEmpty())
+    {
+      return null;
+    }
+
+    return values.get(0);
   }
 
 
 
   /**
-   * Specifies the path to the schema file that contains the
+   * Specifies the name of the schema file that contains the
    * definition for this DIT content rule.
    *
-   * @param  schemaFile  The path to the schema file that contains the
+   * @param  schemaFile  The name of the schema file that contains the
    *                     definition for this DIT content rule.
    */
   public void setSchemaFile(String schemaFile)
@@ -332,7 +377,7 @@
     assert debugEnter(CLASS_NAME, "setSchemaFile",
                       String.valueOf(schemaFile));
 
-    this.schemaFile = schemaFile;
+    setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
   }
 
 
@@ -341,7 +386,7 @@
    * Retrieves the description for this DIT content rule.
    *
    * @return  The description for this DIT content rule, or
-   *          <CODE>null</CODE> if there is none.
+   *          {@code null} if there is none.
    */
   public String getDescription()
   {
@@ -353,28 +398,13 @@
 
 
   /**
-   * Specifies the description for this DIT content rule.
-   *
-   * @param  description  The description for this DIT content rule.
-   */
-  public void setDescription(String description)
-  {
-    assert debugEnter(CLASS_NAME, "setDescription",
-                      String.valueOf(description));
-
-    this.description = description;
-  }
-
-
-
-  /**
    * Retrieves the set of auxiliary objectclasses that may be used for
    * entries associated with this DIT content rule.
    *
    * @return  The set of auxiliary objectclasses that may be used for
    *          entries associated with this DIT content rule.
    */
-  public CopyOnWriteArraySet<ObjectClass> getAuxiliaryClasses()
+  public Set<ObjectClass> getAuxiliaryClasses()
   {
     assert debugEnter(CLASS_NAME, "getAuxiliaryClasses");
 
@@ -384,68 +414,15 @@
 
 
   /**
-   * Specifies the set of auxiliary objectclasses that may be used for
-   * entries associated with this DIT content rule.
-   *
-   * @param  auxiliaryClasses  The set of auxiliary objectclasses that
-   *                           may be used for entries associated with
-   *                           this DIT content rule.
-   */
-  public void setAuxiliaryClasses(
-                   CopyOnWriteArraySet<ObjectClass> auxiliaryClasses)
-  {
-    assert debugEnter(CLASS_NAME, "setAuxiliaryClasses",
-                      String.valueOf(auxiliaryClasses));
-
-    this.auxiliaryClasses = auxiliaryClasses;
-  }
-
-
-
-  /**
-   * Adds the specified auxiliary objectclass to this DIT content
-   * rule.
-   *
-   * @param  auxiliaryClass  The auxiliary class to add to this DIT
-   *                         content rule.
-   */
-  public void addAuxiliaryClass(ObjectClass auxiliaryClass)
-  {
-    assert debugEnter(CLASS_NAME, "addAuxiliaryClass",
-                      String.valueOf(auxiliaryClass));
-
-    auxiliaryClasses.add(auxiliaryClass);
-  }
-
-
-
-  /**
-   * Removes the specified auxiliary objectclass from this DIT content
-   * rule.
-   *
-   * @param  auxiliaryClass  The auxiliary class to remove from this
-   *                         DIT content rule.
-   */
-  public void removeAuxiliaryClass(ObjectClass auxiliaryClass)
-  {
-    assert debugEnter(CLASS_NAME, "removeAuxiliaryClass",
-                      String.valueOf(auxiliaryClass));
-
-    auxiliaryClasses.remove(auxiliaryClass);
-  }
-
-
-
-  /**
    * Indicates whether the provided auxiliary objectclass is allowed
    * for use by this DIT content rule.
    *
    * @param  auxiliaryClass  The auxiliary objectclass for which to
    *                         make the determination.
    *
-   * @return  <CODE>true</CODE> if the provided auxiliary objectclass
-   *          is allowed for use by this DIT content rule, or
-   *          <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided auxiliary objectclass is
+   *          allowed for use by this DIT content rule, or
+   *          {@code false} if not.
    */
   public boolean isAllowedAuxiliaryClass(ObjectClass auxiliaryClass)
   {
@@ -463,7 +440,7 @@
    * @return  The set of required attributes for this DIT content
    *          rule.
    */
-  public CopyOnWriteArraySet<AttributeType> getRequiredAttributes()
+  public Set<AttributeType> getRequiredAttributes()
   {
     assert debugEnter(CLASS_NAME, "getRequiredAttributes");
 
@@ -479,9 +456,8 @@
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          required by this DIT content rule, or <CODE>false</CODE>
-   *          if not.
+   * @return  {@code true} if the provided attribute type is required
+   *          by this DIT content rule, or {@code false} if not.
    */
   public boolean isRequired(AttributeType attributeType)
   {
@@ -494,67 +470,13 @@
 
 
   /**
-   * Specifies the set of required attributes for this DIT content
-   * rule.
-   *
-   * @param  requiredAttributes  The set of required attributes for
-   *                             this DIT content rule.
-   */
-  public void setRequiredAttributes(CopyOnWriteArraySet<AttributeType>
-                                         requiredAttributes)
-  {
-    assert debugEnter(CLASS_NAME, "setRequiredAttributes",
-                      String.valueOf(requiredAttributes));
-
-    this.requiredAttributes = requiredAttributes;
-  }
-
-
-
-  /**
-   * Adds the provided attribute to the set of required attributes for
-   * this DIT content rule.
-   *
-   * @param  attributeType  The attribute type to add to the set of
-   *                        required attributes for this DIT content
-   *                        rule.
-   */
-  public void addRequiredAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "addRequiredAttribute",
-                      String.valueOf(attributeType));
-
-    requiredAttributes.add(attributeType);
-  }
-
-
-
-  /**
-   * Removes the provided attribute from the set of required
-   * attributes for this DIT content rule.
-   *
-   * @param  attributeType  The attribute type to remove from the set
-   *                        of required attributes for this DIT
-   *                        content rule.
-   */
-  public void removeRequiredAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "removeRequiredAttribute",
-                      String.valueOf(attributeType));
-
-    requiredAttributes.remove(attributeType);
-  }
-
-
-
-  /**
    * Retrieves the set of optional attributes for this DIT content
    * rule.
    *
    * @return  The set of optional attributes for this DIT content
    *          rule.
    */
-  public CopyOnWriteArraySet<AttributeType> getOptionalAttributes()
+  public Set<AttributeType> getOptionalAttributes()
   {
     assert debugEnter(CLASS_NAME, "getOptionalAttributes");
 
@@ -570,9 +492,8 @@
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          optional for this DIT content rule, or
-   *          <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided attribute type is optional
+   *          for this DIT content rule, or {@code false} if not.
    */
   public boolean isOptional(AttributeType attributeType)
   {
@@ -585,69 +506,15 @@
 
 
   /**
-   * Specifies the set of optional attributes for this DIT content
-   * rule.
-   *
-   * @param  optionalAttributes  The set of optional attributes for
-   *                             this DIT content rule.
-   */
-  public void setOptionalAttributes(CopyOnWriteArraySet<AttributeType>
-                                         optionalAttributes)
-  {
-    assert debugEnter(CLASS_NAME, "setOptionalAttributes",
-                      String.valueOf(optionalAttributes));
-
-    this.optionalAttributes = optionalAttributes;
-  }
-
-
-
-  /**
-   * Adds the provided attribute to the set of optional attributes for
-   * this DIT content rule.
-   *
-   * @param  attributeType  The attribute type to add to the set of
-   *                        optional attributes for this DIT content
-   *                        rule.
-   */
-  public void addOptionalAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "addOptionalAttribute",
-                      String.valueOf(attributeType));
-
-    optionalAttributes.add(attributeType);
-  }
-
-
-
-  /**
-   * Removes the provided attribute from the set of optional
-   * attributes for this DIT content rule.
-   *
-   * @param  attributeType  The attribute type to remove from the set
-   *                        of optional attributes for this DIT
-   *                        content rule.
-   */
-  public void removeOptionalAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "removeOptionalAttribute",
-                      String.valueOf(attributeType));
-
-    optionalAttributes.remove(attributeType);
-  }
-
-
-
-  /**
    * Indicates whether the provided attribute type is in the list of
    * required or optional attributes for this DIT content rule.
    *
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          required or allowed for this DIT content rule, or
-   *          <CODE>false</CODE> if it is not.
+   * @return  {@code true} if the provided attribute type is required
+   *          or allowed for this DIT content rule, or {@code false}
+   *          if it is not.
    */
   public boolean isRequiredOrOptional(AttributeType attributeType)
   {
@@ -672,9 +539,9 @@
    *                        allowed for an objectclass will be
    *                        acceptable.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          required or allowed for this DIT content rule, or
-   *          <CODE>false</CODE> if it is not.
+   * @return  {@code true} if the provided attribute type is required
+   *          or allowed for this DIT content rule, or {@code false}
+   *          if it is not.
    */
   public boolean isRequiredOrOptional(AttributeType attributeType,
                                       boolean acceptEmpty)
@@ -702,7 +569,7 @@
    * @return  The set of prohibited attributes for this DIT content
    *          rule.
    */
-  public CopyOnWriteArraySet<AttributeType> getProhibitedAttributes()
+  public Set<AttributeType> getProhibitedAttributes()
   {
     assert debugEnter(CLASS_NAME, "getProhibitedAttributes");
 
@@ -718,9 +585,9 @@
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          prohibited for this DIT content rule, or
-   *          <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided attribute type is
+   *          prohibited for this DIT content rule, or {@code false}
+   *          if not.
    */
   public boolean isProhibited(AttributeType attributeType)
   {
@@ -733,65 +600,10 @@
 
 
   /**
-   * Specifies the set of prohibited attributes for this DIT content
-   * rule.
-   *
-   * @param  prohibitedAttributes  The set of prohibited attributes
-   *                               for this DIT content rule.
-   */
-  public void setProhibitedAttributes(
-                   CopyOnWriteArraySet<AttributeType>
-                       prohibitedAttributes)
-  {
-    assert debugEnter(CLASS_NAME, "setProhibitedAttributes",
-                      String.valueOf(prohibitedAttributes));
-
-    this.prohibitedAttributes = prohibitedAttributes;
-  }
-
-
-
-  /**
-   * Adds the provided attribute to the set of prohibited attributes
-   * for this DIT content rule.
-   *
-   * @param  attributeType  The attribute type to add to the set of
-   *                        prohibited attributes for this DIT
-   *                        content rule.
-   */
-  public void addProhibitedAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "addProhibitedAttribute",
-                      String.valueOf(attributeType));
-
-    prohibitedAttributes.add(attributeType);
-  }
-
-
-
-  /**
-   * Removes the provided attribute from the set of prohibited
-   * attributes for this DIT content rule.
-   *
-   * @param  attributeType  The attribute type to remove from the set
-   *                        of prohibited attributes for this DIT
-   *                        content rule.
-   */
-  public void removeProhibitedAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "removeProhibitedAttribute",
-                      String.valueOf(attributeType));
-
-    prohibitedAttributes.remove(attributeType);
-  }
-
-
-
-  /**
    * Indicates whether this DIT content rule is declared "obsolete".
    *
-   * @return  <CODE>true</CODE> if this DIT content rule is declared
-   *          "obsolete", or <CODE>false</CODE> if it is not.
+   * @return  {@code true} if this DIT content rule is declared
+   *          "obsolete", or {@code false} if it is not.
    */
   public boolean isObsolete()
   {
@@ -803,33 +615,15 @@
 
 
   /**
-   * Specifies whether this DIT content rule is declared "obsolete".
-   *
-   * @param  isObsolete  Specifies whether this DIT content rule is
-   *                     declared "obsolete".
-   */
-  public void setObsolete(boolean isObsolete)
-  {
-    assert debugEnter(CLASS_NAME, "setObsolete",
-                      String.valueOf(isObsolete));
-
-    this.isObsolete = isObsolete;
-  }
-
-
-
-  /**
    * Retrieves a mapping between the names of any extra non-standard
    * properties that may be associated with this DIT content rule and
-   * the value for that property.  The caller may alter the contents
-   * of this mapping.
+   * the value for that property.
    *
    * @return  A mapping between the names of any extra non-standard
    *          properties that may be associated with this DIT content
    *          rule and the value for that property.
    */
-  public ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-              getExtraProperties()
+  public Map<String,List<String>> getExtraProperties()
   {
     assert debugEnter(CLASS_NAME, "getExtraProperties");
 
@@ -846,11 +640,10 @@
    *                       to retrieve the value.
    *
    * @return  The value of the specified "extra" property for this DIT
-   *          content rule, or <CODE>null</CODE> if no such property
-   *          is defined.
+   *          content rule, or {@code null} if no such property is
+   *          defined.
    */
-  public CopyOnWriteArrayList<String>
-              getExtraProperty(String propertyName)
+  public List<String> getExtraProperty(String propertyName)
   {
     assert debugEnter(CLASS_NAME, "getExtraProperty",
                       String.valueOf(propertyName));
@@ -861,6 +654,66 @@
 
 
   /**
+   * Specifies the provided "extra" property for this DIT content
+   * rule.
+   *
+   * @param  name   The name for the "extra" property.  It must not be
+   *                {@code null}.
+   * @param  value  The value for the "extra" property, or
+   *                {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, String value)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(value));
+
+    ensureNotNull(name);
+
+    if (value == null)
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> values = new LinkedList<String>();
+      values.add(value);
+
+      extraProperties.put(name, values);
+    }
+  }
+
+
+
+  /**
+   * Specifies the provided "extra" property for this DIT content
+   * rule.
+   *
+   * @param  name    The name for the "extra" property.  It must not
+   *                 be {@code null}.
+   * @param  values  The set of value for the "extra" property, or
+   *                 {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, List<String> values)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(values));
+
+    ensureNotNull(name);
+
+    if ((values == null) || values.isEmpty())
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> valuesCopy = new LinkedList<String>(values);
+      extraProperties.put(name, valuesCopy);
+    }
+  }
+
+
+
+  /**
    * Indicates whether the provided object is equal to this DIT
    * content rule.  The object will be considered equal if it is a DIT
    * content rule for the same structural objectclass and the same
@@ -871,8 +724,8 @@
    *
    * @param  o  The object for which to make the determination.
    *
-   * @return  <CODE>true</CODE> if the provided object is equal to
-   *          this DIT content rule, or <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided object is equal to
+   *          this DIT content rule, or {@code false} if not.
    */
   public boolean equals(Object o)
   {
@@ -1112,8 +965,13 @@
     {
       for (String property : extraProperties.keySet())
       {
-        CopyOnWriteArrayList<String> valueList =
-             extraProperties.get(property);
+        if ((! includeFileElement) &&
+            property.equals(SCHEMA_PROPERTY_FILENAME))
+        {
+          continue;
+        }
+
+        List<String> valueList = extraProperties.get(property);
 
         buffer.append(" ");
         buffer.append(property);
@@ -1140,16 +998,6 @@
       }
     }
 
-    if (includeFileElement && (schemaFile != null) &&
-        (! extraProperties.containsKey(SCHEMA_PROPERTY_FILENAME)))
-    {
-      buffer.append(" ");
-      buffer.append(SCHEMA_PROPERTY_FILENAME);
-      buffer.append(" '");
-      buffer.append(schemaFile);
-      buffer.append("'");
-    }
-
     buffer.append(" )");
   }
 }
diff --git a/opends/src/server/org/opends/server/types/DITStructureRule.java b/opends/src/server/org/opends/server/types/DITStructureRule.java
index aa39250..9046ef2 100644
--- a/opends/src/server/org/opends/server/types/DITStructureRule.java
+++ b/opends/src/server/org/opends/server/types/DITStructureRule.java
@@ -22,16 +22,21 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
 
 
 import java.util.Iterator;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opends.server.schema.DITStructureRuleSyntax;
 
 import static org.opends.server.loggers.Debug.*;
 import static org.opends.server.loggers.Error.*;
@@ -39,6 +44,7 @@
 import static org.opends.server.messages.MessageHandler.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
+import static org.opends.server.util.Validator.*;
 
 
 
@@ -46,7 +52,8 @@
  * This class defines a DIT structure rule, which is used to indicate
  * the types of children that entries may have.
  */
-public class DITStructureRule
+public final class DITStructureRule
+       implements SchemaFileElement
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -57,38 +64,39 @@
 
 
   // Indicates whether this DIT structure rule is declared "obsolete".
-  private boolean isObsolete;
+  private final boolean isObsolete;
+
+  // The rule ID for this DIT structure rule.
+  private final int ruleID;
+
+  // The name form for this DIT structure rule.
+  private final NameForm nameForm;
 
   // The set of additional name-value pairs associated with this DIT
   // structure rule.
-  private ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-               extraProperties;
+  private final Map<String,List<String>> extraProperties;
 
   // The set of names for this DIT structure rule, in a mapping
   // between the all-lowercase form and the user-defined form.
-  private ConcurrentHashMap<String,String> names;
+  private final Map<String,String> names;
 
   // The set of superior DIT structure rules.
-  private CopyOnWriteArraySet<DITStructureRule> superiorRules;
+  private final Set<DITStructureRule> superiorRules;
 
-  // The rule ID for this DIT structure rule.
-  private int ruleID;
-
-  // The name form for this DIT structure rule.
-  private NameForm nameForm;
+  // The definition string for this DIT structure rule.
+  private final String definition;
 
   // The description for this DIT structure rule.
-  private String description;
-
-  // The path to the schema file that contains this DIT structure rule
-  // definition.
-  private String schemaFile;
+  private final String description;
 
 
 
   /**
    * Creates a new DIT structure rule with the provided information.
    *
+   * @param  definition       The definition string used to create
+   *                          this DIT structure rule.  It must not be
+   *                          {@code null}.
    * @param  names            The set of names for this DIT structure
    *                          rule, mapping the lowercase names to the
    *                          user-defined values.
@@ -104,33 +112,104 @@
    * @param  extraProperties  The set of "extra" properties associated
    *                          with this DIT structure rules.
    */
-  public DITStructureRule(ConcurrentHashMap<String,String> names,
-              int ruleID, String description,  boolean isObsolete,
-              NameForm nameForm,
-              CopyOnWriteArraySet<DITStructureRule> superiorRules,
-              ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-                   extraProperties)
+  public DITStructureRule(String definition, Map<String,String> names,
+                          int ruleID, String description,
+                          boolean isObsolete, NameForm nameForm,
+                          Set<DITStructureRule> superiorRules,
+                          Map<String,List<String>> extraProperties)
   {
-    assert debugConstructor(CLASS_NAME,
-                            new String[]
-                            {
-                              String.valueOf(names),
-                              String.valueOf(ruleID),
-                              String.valueOf(description),
-                              String.valueOf(isObsolete),
-                              String.valueOf(nameForm),
-                              String.valueOf(superiorRules),
-                              String.valueOf(extraProperties)
-                            });
+    assert debugConstructor(CLASS_NAME, String.valueOf(definition),
+                            String.valueOf(names),
+                            String.valueOf(ruleID),
+                            String.valueOf(description),
+                            String.valueOf(isObsolete),
+                            String.valueOf(nameForm),
+                            String.valueOf(superiorRules),
+                            String.valueOf(extraProperties));
 
-    this.names           = names;
-    this.ruleID          = ruleID;
-    this.description     = description;
-    this.isObsolete      = isObsolete;
-    this.nameForm        = nameForm;
-    this.superiorRules   = superiorRules;
-    this.schemaFile      = null;
-    this.extraProperties = extraProperties;
+    ensureNotNull(definition);
+
+    this.definition  = definition;
+    this.ruleID      = ruleID;
+    this.description = description;
+    this.isObsolete  = isObsolete;
+    this.nameForm    = nameForm;
+
+    if ((names == null) || names.isEmpty())
+    {
+      this.names = new LinkedHashMap<String,String>(0);
+    }
+    else
+    {
+      this.names = new LinkedHashMap<String,String>(names);
+    }
+
+    if ((superiorRules == null) || superiorRules.isEmpty())
+    {
+      this.superiorRules = new LinkedHashSet<DITStructureRule>(0);
+    }
+    else
+    {
+      this.superiorRules =
+           new LinkedHashSet<DITStructureRule>(superiorRules);
+    }
+
+    if ((extraProperties == null) || extraProperties.isEmpty())
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(0);
+    }
+    else
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(extraProperties);
+    }
+  }
+
+
+
+  /**
+   * Retrieves the definition string used to create this DIT structure
+   * rule.
+   *
+   * @return  The definition string used to create this DIT structure
+   *          rule.
+   */
+  public String getDefinition()
+  {
+    assert debugEnter(CLASS_NAME, "getDefinition");
+
+    return definition;
+  }
+
+
+
+  /**
+   * Creates a new instance of this DIT structure rule based on the
+   * definition string.  It will also preserve other state information
+   * associated with this DIT structure rule that is not included in
+   * the definition string (e.g., the name of the schema file with
+   * which it is associated).
+   *
+   * @return  The new instance of this DIT structure rule based on the
+   *          definition string.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to create a new DIT structure rule
+   *                              instance from the definition string.
+   */
+  public DITStructureRule recreateFromDefinition()
+         throws DirectoryException
+  {
+    ByteString value  = ByteStringFactory.create(definition);
+    Schema     schema = DirectoryConfig.getSchema();
+
+    DITStructureRule dsr =
+         DITStructureRuleSyntax.decodeDITStructureRule(value, schema,
+                                                       false);
+    dsr.setSchemaFile(getSchemaFile());
+
+    return dsr;
   }
 
 
@@ -144,7 +223,7 @@
    * @return  The set of names that may be used to reference this DIT
    *          structure rule.
    */
-  public ConcurrentHashMap<String,String> getNames()
+  public Map<String,String> getNames()
   {
     assert debugEnter(CLASS_NAME, "getNames");
 
@@ -154,31 +233,13 @@
 
 
   /**
-   * Specifies the set of names that may be used to reference this DIT
-   * structure rule.  The provided set must contain a mapping between
-   * each name in all lowercase characters and the name in a
-   * user-defined form (which may include mixed capitalization).
-   *
-   * @param  names  The set of names that may be used to reference
-   *                this attribute type.
-   */
-  public void setNames(ConcurrentHashMap<String,String> names)
-  {
-    assert debugEnter(CLASS_NAME, "setNames", String.valueOf(names));
-
-    this.names = names;
-  }
-
-
-
-  /**
    * Indicates whether this DIT structure rule has the specified name.
    *
    * @param  lowerName  The lowercase name for which to make the
    *                    determination.
    *
-   * @return  <CODE>true</CODE> if the specified name is assigned to
-   *          this DIT structure rule, or <CODE>false</CODE> if not.
+   * @return  {@code true} if the specified name is assigned to this
+   *          DIT structure rule, or {@code false} if not.
    */
   public boolean hasName(String lowerName)
   {
@@ -191,41 +252,6 @@
 
 
   /**
-   * Adds the specified name to the set of names for this DIT
-   * structure rule.
-   *
-   * @param  name  The name to add to the set of names for this DIT
-   *               structure rule.
-   */
-  public void addName(String name)
-  {
-    assert debugEnter(CLASS_NAME, "addName", String.valueOf(name));
-
-    String lowerName = toLowerCase(name);
-    names.put(lowerName, name);
-  }
-
-
-
-  /**
-   * Removes the specified name from the set of names for this DIT
-   * structure rule.  This will have no effect if the specified name
-   * is not associated with this attribute type.
-   *
-   * @param  lowerName  The lowercase name to remove from the set of
-   *                    names for this DIT structure rule.
-   */
-  public void removeName(String lowerName)
-  {
-    assert debugEnter(CLASS_NAME, "removeName",
-                      String.valueOf(lowerName));
-
-    names.remove(lowerName);
-  }
-
-
-
-  /**
    * Retrieves the rule ID for this DIT structure rule.
    *
    * @return  The rule ID for this DIT structure rule.
@@ -240,21 +266,6 @@
 
 
   /**
-   * Specifies the rule ID for this DIT structure rule.
-   *
-   * @param  ruleID  The rule ID for this DIT structure rule.
-   */
-  public void setRuleID(int ruleID)
-  {
-    assert debugEnter(CLASS_NAME, "setRuleID",
-                      String.valueOf(ruleID));
-
-    this.ruleID = ruleID;
-  }
-
-
-
-  /**
    * Retrieves the name or rule ID for this DIT structure rule.  If it
    * has one or more names, then the primary name will be returned.
    * If it does not have any names, then the rule ID will be returned.
@@ -278,44 +289,25 @@
 
 
   /**
-   * Indicates whether this DIT structure rule has the specified name
-   * or rule ID.
-   *
-   * @param  lowerValue  The lowercase value for which to make the
-   *                     determination.
-   *
-   * @return  <CODE>true</CODE> if the provided value matches the rule
-   *          ID or one of the names assigned to this DIT structure
-   *          rule, or <CODE>false</CODE> if not.
-   */
-  public boolean hasNameOrOID(String lowerValue)
-  {
-    assert debugEnter(CLASS_NAME, "hasNameOrOID",
-                      String.valueOf(lowerValue));
-
-    if (names.containsKey(lowerValue))
-    {
-      return true;
-    }
-
-    return lowerValue.equals(String.valueOf(ruleID));
-  }
-
-
-
-  /**
    * Retrieves the path to the schema file that contains the
    * definition for this DIT structure rule.
    *
    * @return  The path to the schema file that contains the definition
-   *          for this DIT structure rule, or <CODE>null</CODE> if it
+   *          for this DIT structure rule, or {@code null} if it
    *          is not known or if it is not stored in any schema file.
    */
   public String getSchemaFile()
   {
     assert debugEnter(CLASS_NAME, "getSchemaFile");
 
-    return schemaFile;
+    List<String> values =
+         extraProperties.get(SCHEMA_PROPERTY_FILENAME);
+    if ((values == null) || values.isEmpty())
+    {
+      return null;
+    }
+
+    return values.get(0);
   }
 
 
@@ -332,7 +324,7 @@
     assert debugEnter(CLASS_NAME, "setSchemaFile",
                       String.valueOf(schemaFile));
 
-    this.schemaFile = schemaFile;
+    setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
   }
 
 
@@ -352,20 +344,6 @@
 
 
   /**
-   * Specifies the description for this DIT structure rule.
-   *
-   * @param  description  The description for this DIT structure rule.
-   */
-  public void setDescription(String description)
-  {
-    assert debugEnter(CLASS_NAME, "setDescription", description);
-
-    this.description = description;
-  }
-
-
-
-  /**
    * Retrieves the name form for this DIT structure rule.
    *
    * @return  The name form for this DIT structure rule.
@@ -380,21 +358,6 @@
 
 
   /**
-   * Specifies the name form for this DIT structure rule.
-   *
-   * @param  nameForm  The name form for this DIT structure rule.
-   */
-  public void setNameForm(NameForm nameForm)
-  {
-    assert debugEnter(CLASS_NAME, "setNameForm",
-                      String.valueOf(nameForm));
-
-    this.nameForm = nameForm;
-  }
-
-
-
-  /**
    * Retrieves the structural objectclass for the name form with which
    * this DIT structure rule is associated.
    *
@@ -415,7 +378,7 @@
    *
    * @return  The set of superior rules for this DIT structure rule.
    */
-  public CopyOnWriteArraySet<DITStructureRule> getSuperiorRules()
+  public Set<DITStructureRule> getSuperiorRules()
   {
     assert debugEnter(CLASS_NAME, "getSuperiorRules");
 
@@ -428,8 +391,8 @@
    * Indicates whether this DIT structure rule has one or more
    * superior rules.
    *
-   * @return  <CODE>true</CODE> if this DIT structure rule has one or
-   *          more superior rules, or <CODE>false</CODE> if not.
+   * @return  {@code true} if this DIT structure rule has one or more
+   *          superior rules, or {@code false} if not.
    */
   public boolean hasSuperiorRules()
   {
@@ -441,62 +404,10 @@
 
 
   /**
-   * Specifies the set of superior rules for this DIT structure rule.
-   *
-   * @param  superiorRules  The set of superior rules for this DIT
-   *                        structure rule.
-   */
-  public void setSuperiorRules(CopyOnWriteArraySet<DITStructureRule>
-                                    superiorRules)
-  {
-    assert debugEnter(CLASS_NAME, "setSuperiorRules",
-                      String.valueOf(superiorRules));
-
-    this.superiorRules = superiorRules;
-  }
-
-
-
-  /**
-   * Adds the provided rule as a superior rule for this DIT structure
-   * rule.
-   *
-   * @param  superiorRule  The superior rule to add to this DIT
-   *                       structure rule.
-   */
-  public void addSuperiorRule(DITStructureRule superiorRule)
-  {
-    assert debugEnter(CLASS_NAME, "addSuperiorRule",
-                      String.valueOf(superiorRule));
-
-    superiorRules.add(superiorRule);
-  }
-
-
-
-  /**
-   * Removes the provided rule as a superior rule for this DIT
-   * structure rule.  It will have no effect if the provided rule is
-   * not a superior rule for this DIT structure rule.
-   *
-   * @param  superiorRule  The superior rule to remove from this DIT
-   *                       structure rule.
-   */
-  public void removeSuperiorRule(DITStructureRule superiorRule)
-  {
-    assert debugEnter(CLASS_NAME, "removeSuperiorRule",
-                      String.valueOf(superiorRule));
-
-    superiorRules.remove(superiorRule);
-  }
-
-
-
-  /**
    * Indicates whether this DIT structure rule is declared "obsolete".
    *
-   * @return  <CODE>true</CODE> if this DIT structure rule is declared
-   *          "obsolete", or <CODE>false</CODE> if not.
+   * @return  {@code true} if this DIT structure rule is declared
+   *          "obsolete", or {@code false} if not.
    */
   public boolean isObsolete()
   {
@@ -508,33 +419,15 @@
 
 
   /**
-   * Specifies whether this DIT structure rule is declared "obsolete".
-   *
-   * @param  isObsolete  Specifies whether this DIT structure rule is
-   *                     declared "obsolete".
-   */
-  public void setObsolete(boolean isObsolete)
-  {
-    assert debugEnter(CLASS_NAME, "setObsolete",
-                      String.valueOf(isObsolete));
-
-    this.isObsolete = isObsolete;
-  }
-
-
-
-  /**
    * Retrieves a mapping between the names of any extra non-standard
    * properties that may be associated with this DIT structure rule
-   * and the value for that property.  The caller may alter the
-   * contents of this mapping.
+   * and the value for that property.
    *
    * @return  A mapping between the names of any extra non-standard
    *          properties that may be associated with this DIT
    *          structure rule and the value for that property.
    */
-  public ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-              getExtraProperties()
+  public Map<String,List<String>> getExtraProperties()
   {
     assert debugEnter(CLASS_NAME, "getExtraProperties");
 
@@ -551,11 +444,10 @@
    *                       to retrieve the value.
    *
    * @return  The value of the specified "extra" property for this DIT
-   *          structure rule, or <CODE>null</CODE> if no such property
-   *          is defined.
+   *          structure rule, or {@code null} if no such property is
+   *          defined.
    */
-  public CopyOnWriteArrayList<String>
-              getExtraProperty(String propertyName)
+  public List<String> getExtraProperty(String propertyName)
   {
     assert debugEnter(CLASS_NAME, "getExtraProperty",
                       String.valueOf(propertyName));
@@ -566,14 +458,74 @@
 
 
   /**
+   * Specifies the provided "extra" property for this DIT structure
+   * rule.
+   *
+   * @param  name   The name for the "extra" property.  It must not be
+   *                {@code null}.
+   * @param  value  The value for the "extra" property, or
+   *                {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, String value)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(value));
+
+    ensureNotNull(name);
+
+    if (value == null)
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> values = new LinkedList<String>();
+      values.add(value);
+
+      extraProperties.put(name, values);
+    }
+  }
+
+
+
+  /**
+   * Specifies the provided "extra" property for this DIT structure
+   * rule.
+   *
+   * @param  name    The name for the "extra" property.  It must not
+   *                 be {@code null}.
+   * @param  values  The set of value for the "extra" property, or
+   *                 {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, List<String> values)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(values));
+
+    ensureNotNull(name);
+
+    if ((values == null) || values.isEmpty())
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> valuesCopy = new LinkedList<String>(values);
+      extraProperties.put(name, valuesCopy);
+    }
+  }
+
+
+
+  /**
    * Indicates whether the provided object is equal to this DIT
    * structure rule.  The object will be considered equal if it is a
    * DIT structure rule with the same OID as the current type.
    *
    * @param  o  The object for which to make the determination.
    *
-   * @return  <CODE>true</CODE> if the provided object is equal to
-   *          this attribute, or <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided object is equal to this
+   *          attribute, or {@code false} if not.
    */
   public boolean equals(Object o)
   {
@@ -718,8 +670,13 @@
     {
       for (String property : extraProperties.keySet())
       {
-        CopyOnWriteArrayList<String> valueList =
-             extraProperties.get(property);
+        if ((! includeFileElement) &&
+            property.equals(SCHEMA_PROPERTY_FILENAME))
+        {
+          continue;
+        }
+
+        List<String> valueList = extraProperties.get(property);
 
         buffer.append(" ");
         buffer.append(property);
@@ -746,16 +703,6 @@
       }
     }
 
-    if (includeFileElement && (schemaFile != null) &&
-        (! extraProperties.containsKey(SCHEMA_PROPERTY_FILENAME)))
-    {
-      buffer.append(" ");
-      buffer.append(SCHEMA_PROPERTY_FILENAME);
-      buffer.append(" '");
-      buffer.append(schemaFile);
-      buffer.append("'");
-    }
-
     buffer.append(" )");
   }
 }
diff --git a/opends/src/server/org/opends/server/types/MatchingRuleUse.java b/opends/src/server/org/opends/server/types/MatchingRuleUse.java
index 4cef2b5..7190358 100644
--- a/opends/src/server/org/opends/server/types/MatchingRuleUse.java
+++ b/opends/src/server/org/opends/server/types/MatchingRuleUse.java
@@ -22,22 +22,27 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
 
 
 import java.util.Iterator;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import org.opends.server.api.MatchingRule;
+import org.opends.server.schema.MatchingRuleUseSyntax;
 
 import static org.opends.server.loggers.Debug.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
+import static org.opends.server.util.Validator.*;
 
 
 
@@ -47,7 +52,8 @@
  * the set of attribute types that may be used for a given matching
  * rule.
  */
-public class MatchingRuleUse
+public final class MatchingRuleUse
+       implements SchemaFileElement
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -58,32 +64,30 @@
 
 
   // Indicates whether this matching rule use is declared "obsolete".
-  private boolean isObsolete;
+  private final boolean isObsolete;
+
+  // The set of additional name-value pairs associated with this
+  // matching rule use definition.
+  private final Map<String,List<String>> extraProperties;
 
   // The set of names that may be used to refer to this matching rule
   // use, mapped between their all-lowercase representations and the
   // user-defined representations.
-  private ConcurrentHashMap<String,String> names;
-
-  // The set of attribute types with which this matching rule use is
-  // associated.
-  private CopyOnWriteArraySet<AttributeType> attributes;
-
-  // The set of additional name-value pairs associated with this
-  // matching rule use definition.
-  private ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-               extraProperties;
+  private final Map<String,String> names;
 
   // The matching rule with which this matching rule use is
   // associated.
-  private MatchingRule matchingRule;
+  private final MatchingRule matchingRule;
+
+  // The set of attribute types with which this matching rule use is
+  // associated.
+  private final Set<AttributeType> attributes;
+
+  // The definition string used to create this matching rule use.
+  private final String definition;
 
   // The description for this matching rule use.
-  private String description;
-
-  // The path to the schema file that contains this matching rule use
-  // definition.
-  private String schemaFile;
+  private final String description;
 
 
 
@@ -91,8 +95,11 @@
    * Creates a new matching rule use definition with the provided
    * information.
    *
+   * @param  definition       The definition string used to create
+   *                          this matching rule use.  It must not be
+   *                          {@code null}.
    * @param  matchingRule     The matching rule for this matching rule
-   *                          use.
+   *                          use.  It must not be {@code null}.
    * @param  names            The set of names for this matching rule
    *                          use.
    * @param  description      The description for this matching rule
@@ -104,32 +111,100 @@
    * @param  extraProperties  A set of "extra" properties that may be
    *                          associated with this matching rule use.
    */
-  public MatchingRuleUse(MatchingRule matchingRule,
-              ConcurrentHashMap<String,String> names,
-              String description, boolean isObsolete,
-              CopyOnWriteArraySet<AttributeType> attributes,
-              ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-                   extraProperties)
+  public MatchingRuleUse(String definition, MatchingRule matchingRule,
+                         Map<String,String> names, String description,
+                         boolean isObsolete,
+                         Set<AttributeType> attributes,
+                         Map<String,List<String>> extraProperties)
   {
-    assert debugConstructor(CLASS_NAME,
-                            new String[]
-                            {
-                              String.valueOf(matchingRule),
-                              String.valueOf(names),
-                              String.valueOf(description),
-                              String.valueOf(isObsolete),
-                              String.valueOf(attributes),
-                              String.valueOf(extraProperties)
-                            });
+    assert debugConstructor(CLASS_NAME, String.valueOf(definition),
+                            String.valueOf(matchingRule),
+                            String.valueOf(names),
+                            String.valueOf(description),
+                            String.valueOf(isObsolete),
+                            String.valueOf(attributes),
+                            String.valueOf(extraProperties));
+
+    ensureNotNull(definition, matchingRule);
+
+    this.definition   = definition;
+    this.matchingRule = matchingRule;
+    this.description  = description;
+    this.isObsolete   = isObsolete;
+
+    if ((names == null) || names.isEmpty())
+    {
+      this.names = new LinkedHashMap<String,String>(0);
+    }
+    else
+    {
+      this.names = new LinkedHashMap<String,String>(names);
+    }
+
+    if ((attributes == null) || attributes.isEmpty())
+    {
+      this.attributes = new LinkedHashSet<AttributeType>(0);
+    }
+    else
+    {
+      this.attributes = new LinkedHashSet<AttributeType>(attributes);
+    }
+
+    if ((extraProperties == null) || extraProperties.isEmpty())
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(0);
+    }
+    else
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(extraProperties);
+    }
+  }
 
 
-    this.matchingRule    = matchingRule;
-    this.names           = names;
-    this.description     = description;
-    this.isObsolete      = isObsolete;
-    this.attributes      = attributes;
-    this.schemaFile      = null;
-    this.extraProperties = extraProperties;
+
+  /**
+   * Retrieves the definition string used to create this matching rule
+   * use.
+   *
+   * @return  The definition string used to create this matching rule
+   *          use.
+   */
+  public String getDefinition()
+  {
+    assert debugEnter(CLASS_NAME, "getDefinition");
+
+    return definition;
+  }
+
+
+
+  /**
+   * Creates a new instance of this matching rule use based on the
+   * definition string.  It will also preserve other state information
+   * associated with this matching rule use that is not included in
+   * the definition string (e.g., the name of the schema file with
+   * which it is associated).
+   *
+   * @return  The new instance of this matching rule use based on the
+   *          definition string.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to create a new matching rule use
+   *                              instance from the definition string.
+   */
+  public MatchingRuleUse recreateFromDefinition()
+         throws DirectoryException
+  {
+    ByteString value  = ByteStringFactory.create(definition);
+    Schema     schema = DirectoryConfig.getSchema();
+
+    MatchingRuleUse mru =
+         MatchingRuleUseSyntax.decodeMatchingRuleUse(value, schema);
+    mru.setSchemaFile(getSchemaFile());
+
+    return mru;
   }
 
 
@@ -149,29 +224,13 @@
 
 
   /**
-   * Specifies the matching rule for this matching rule use.
-   *
-   * @param  matchingRule  The matching rule for this matching rule
-   *                       use.
-   */
-  public void setMatchingRule(MatchingRule matchingRule)
-  {
-    assert debugEnter(CLASS_NAME, "setMatchingRule",
-                      String.valueOf(matchingRule));
-
-    this.matchingRule = matchingRule;
-  }
-
-
-
-  /**
    * Retrieves the set of names for this matching rule use.  The
    * mapping will be between the names in all lowercase form and the
    * names in the user-defined form.
    *
    * @return  The set of names for this matching rule use.
    */
-  public ConcurrentHashMap<String,String> getNames()
+  public Map<String,String> getNames()
   {
     assert debugEnter(CLASS_NAME, "getNames");
 
@@ -185,7 +244,7 @@
    * rule use.
    *
    * @return  The primary name to use when referencing this matching
-   *          rule use, or <CODE>null</CODE> if there is none.
+   *          rule use, or {@code null} if there is none.
    */
   public String getName()
   {
@@ -209,8 +268,8 @@
    * @param  lowerName  The name for which to make the determination,
    *                    formatted in all lowercase characters.
    *
-   * @return  <CODE>true</CODE> if this matching rule use has the
-   *          specified name, or <CODE>false</CODE> if not.
+   * @return  {@code true} if this matching rule use has the specified
+   *          name, or {@code false} if not.
    */
   public boolean hasName(String lowerName)
   {
@@ -223,69 +282,25 @@
 
 
   /**
-   * Specifies the set of names for this matching rule use as a
-   * mapping between the names in all lowercase form and the names in
-   * the user-defined form.
-   *
-   * @param  names  The set of names for this matching rule use.
-   */
-  public void setNames(ConcurrentHashMap<String,String> names)
-  {
-    assert debugEnter(CLASS_NAME, "setNames", String.valueOf(names));
-
-    this.names = names;
-  }
-
-
-
-  /**
-   * Adds the provided name to the set of names for this matching rule
-   * use.
-   *
-   * @param  name  The name to add to the set of names for this
-   *               matching rule use.
-   */
-  public void addName(String name)
-  {
-    assert debugEnter(CLASS_NAME, "addName", String.valueOf(name));
-
-    names.put(toLowerCase(name), name);
-  }
-
-
-
-  /**
-   * Removes the provided name from the set of names for this matching
-   * rule use.  This will have no effect if the specified name is not
-   * associated with this matching rule use.
-   *
-   * @param  lowerName  The name to remove from the set of names for
-   *                    this matching rule use, in all lowercase
-   *                    characters.
-   */
-  public void removeName(String lowerName)
-  {
-    assert debugEnter(CLASS_NAME, "removeName",
-                      String.valueOf(lowerName));
-
-    names.remove(lowerName);
-  }
-
-
-
-  /**
    * Retrieves the path to the schema file that contains the
    * definition for this matching rule use.
    *
    * @return  The path to the schema file that contains the definition
-   *          for this matching rule use, or <CODE>null</CODE> if it
-   *          is not known or if it is not stored in any schema file.
+   *          for this matching rule use, or {@code null} if it is not
+   *          known or if it is not stored in any schema file.
    */
   public String getSchemaFile()
   {
     assert debugEnter(CLASS_NAME, "getSchemaFile");
 
-    return schemaFile;
+    List<String> values =
+         extraProperties.get(SCHEMA_PROPERTY_FILENAME);
+    if ((values == null) || values.isEmpty())
+    {
+      return null;
+    }
+
+    return values.get(0);
   }
 
 
@@ -302,7 +317,7 @@
     assert debugEnter(CLASS_NAME, "setSchemaFile",
                       String.valueOf(schemaFile));
 
-    this.schemaFile = schemaFile;
+    setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
   }
 
 
@@ -311,7 +326,7 @@
    * Retrieves the description for this matching rule use.
    *
    * @return  The description for this matching rule use, or
-   *          <CODE>null</CODE> if there is none.
+   *          {@code null} if there is none.
    */
   public String getDescription()
   {
@@ -323,25 +338,10 @@
 
 
   /**
-   * Specifies the description for this matching rule use.
-   *
-   * @param  description  The description for this matching rule use.
-   */
-  public void setDescription(String description)
-  {
-    assert debugEnter(CLASS_NAME, "setDescription",
-                      String.valueOf(description));
-
-    this.description = description;
-  }
-
-
-
-  /**
    * Indicates whether this matching rule use is declared "obsolete".
    *
-   * @return  <CODE>true</CODE> if this matching rule use is declared
-   *          "obsolete", or <CODE>false</CODE> if it is not.
+   * @return  {@code true} if this matching rule use is declared
+   *          "obsolete", or {@code false} if it is not.
    */
   public boolean isObsolete()
   {
@@ -353,29 +353,13 @@
 
 
   /**
-   * Specifies whether this matching rule use is declared "obsolete".
-   *
-   * @param  isObsolete  Specifies whether this matching rule use is
-   *                     declared "obsolete".
-   */
-  public void setObsolete(boolean isObsolete)
-  {
-    assert debugEnter(CLASS_NAME, "setObsolete",
-                      String.valueOf(isObsolete));
-
-    this.isObsolete = isObsolete;
-  }
-
-
-
-  /**
    * Retrieves the set of attributes associated with this matching
    * rule use.
    *
    * @return  The set of attributes associated with this matching
    *          rule use.
    */
-  public CopyOnWriteArraySet<AttributeType> getAttributes()
+  public Set<AttributeType> getAttributes()
   {
     assert debugEnter(CLASS_NAME, "getAttributes");
 
@@ -391,9 +375,9 @@
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          referenced by this matching rule use, or
-   *          <CODE>false</CODE> if it is not.
+   * @return  {@code true} if the provided attribute type is
+   *          referenced by this matching rule use, or {@code false}
+   *          if it is not.
    */
   public boolean appliesToAttribute(AttributeType attributeType)
   {
@@ -406,71 +390,15 @@
 
 
   /**
-   * Specifies the set of attributes for this matching rule use.
-   *
-   * @param  attributes  The set of attributes for this matching rule
-   *                     use.
-   */
-  public void setAttributes(
-                   CopyOnWriteArraySet<AttributeType> attributes)
-  {
-    assert debugEnter(CLASS_NAME, "setAttributes",
-                      String.valueOf(attributes));
-
-    this.attributes = attributes;
-  }
-
-
-
-  /**
-   * Adds the provided attribute type to the set of attributes for
-   * this matching rule use.  This will have no effect if the provided
-   * attribute type is already associated with this matching rule use.
-   *
-   * @param  attributeType  The attribute type to add to the set of
-   *                        attributes for this matching rule use.
-   */
-  public void addAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "addAttribute",
-                      String.valueOf(attributeType));
-
-    attributes.add(attributeType);
-  }
-
-
-
-  /**
-   * Removes the provided attribute type from the set of attributes
-   * for this matching rule use.  This will have no effect if the
-   * provided attribute type is not associated with this matching rule
-   * use.
-   *
-   * @param  attributeType  The attribute type to remove from the set
-   *                        of attributes for this matching rule use.
-   */
-  public void removeAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "addAttribute",
-                      String.valueOf(attributeType));
-
-    attributes.remove(attributeType);
-  }
-
-
-
-  /**
    * Retrieves a mapping between the names of any extra non-standard
    * properties that may be associated with this matching rule use and
-   * the value for that property.  The caller may alter the contents
-   * of this mapping.
+   * the value for that property.
    *
    * @return  A mapping between the names of any extra non-standard
    *          properties that may be associated with this matching
    *          rule use and the value for that property.
    */
-  public ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-              getExtraProperties()
+  public Map<String,List<String>> getExtraProperties()
   {
     assert debugEnter(CLASS_NAME, "getExtraProperties");
 
@@ -487,11 +415,10 @@
    *                       to retrieve the value.
    *
    * @return  The value of the specified "extra" property for this
-   *          matching rule use, or <CODE>null</CODE> if no such
-   *          property is defined.
+   *          matching rule use, or {@code null} if no such property
+   *          is defined.
    */
-  public CopyOnWriteArrayList<String>
-              getExtraProperty(String propertyName)
+  public List<String> getExtraProperty(String propertyName)
   {
     assert debugEnter(CLASS_NAME, "getExtraProperty",
                       String.valueOf(propertyName));
@@ -502,14 +429,74 @@
 
 
   /**
+   * Specifies the provided "extra" property for this matching rule
+   * use.
+   *
+   * @param  name   The name for the "extra" property.  It must not be
+   *                {@code null}.
+   * @param  value  The value for the "extra" property, or
+   *                {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, String value)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(value));
+
+    ensureNotNull(name);
+
+    if (value == null)
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> values = new LinkedList<String>();
+      values.add(value);
+
+      extraProperties.put(name, values);
+    }
+  }
+
+
+
+  /**
+   * Specifies the provided "extra" property for this matching rule
+   * use.
+   *
+   * @param  name    The name for the "extra" property.  It must not
+   *                 be {@code null}.
+   * @param  values  The set of value for the "extra" property, or
+   *                 {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, List<String> values)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(values));
+
+    ensureNotNull(name);
+
+    if ((values == null) || values.isEmpty())
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> valuesCopy = new LinkedList<String>(values);
+      extraProperties.put(name, valuesCopy);
+    }
+  }
+
+
+
+  /**
    * Indicates whether the provided object is equal to this matching
    * rule use.  The object will be considered equal if it is a
    * matching rule use with the same matching rule.
    *
    * @param  o  The object for which to make the determination.
    *
-   * @return  <CODE>true</CODE> if the provided object is equal to
-   *          this matching rule use, or <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided object is equal to this
+   *          matching rule use, or {@code false} if not.
    */
   public boolean equals(Object o)
   {
@@ -647,8 +634,13 @@
     {
       for (String property : extraProperties.keySet())
       {
-        CopyOnWriteArrayList<String> valueList =
-             extraProperties.get(property);
+        if ((! includeFileElement) &&
+            property.equals(SCHEMA_PROPERTY_FILENAME))
+        {
+          continue;
+        }
+
+        List<String> valueList = extraProperties.get(property);
 
         buffer.append(" ");
         buffer.append(property);
@@ -675,16 +667,6 @@
       }
     }
 
-    if (includeFileElement && (schemaFile != null) &&
-        (! extraProperties.containsKey(SCHEMA_PROPERTY_FILENAME)))
-    {
-      buffer.append(" ");
-      buffer.append(SCHEMA_PROPERTY_FILENAME);
-      buffer.append(" '");
-      buffer.append(schemaFile);
-      buffer.append("'");
-    }
-
     buffer.append(" )");
   }
 }
diff --git a/opends/src/server/org/opends/server/types/NameForm.java b/opends/src/server/org/opends/server/types/NameForm.java
index 6ce5bd0..eb5e8b0 100644
--- a/opends/src/server/org/opends/server/types/NameForm.java
+++ b/opends/src/server/org/opends/server/types/NameForm.java
@@ -22,20 +22,26 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
 
 
 import java.util.Iterator;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opends.server.schema.NameFormSyntax;
 
 import static org.opends.server.loggers.Debug.*;
 import static org.opends.server.util.ServerConstants.*;
 import static org.opends.server.util.StaticUtils.*;
+import static org.opends.server.util.Validator.*;
 
 
 
@@ -45,7 +51,8 @@
  * and/or may be used in the RDN of an entry with a given structural
  * objectclass.
  */
-public class NameForm
+public final class NameForm
+       implements SchemaFileElement
 {
   /**
    * The fully-qualified name of this class for debugging purposes.
@@ -56,49 +63,52 @@
 
 
   // Indicates whether this name form is declared "obsolete".
-  private boolean isObsolete;
+  private final boolean isObsolete;
 
   // The set of additional name-value pairs associated with this name
   // form definition.
-  private ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-               extraProperties;
+  private final Map<String,List<String>> extraProperties;
 
   // The mapping between the lowercase names and the user-provided
   // names for this name form.
-  private ConcurrentHashMap<String,String> names;
-
-  // The set of optional attribute types for this name form.
-  private CopyOnWriteArraySet<AttributeType> optionalAttributes;
-
-  // The set of required attribute types for this name form.
-  private CopyOnWriteArraySet<AttributeType> requiredAttributes;
+  private final Map<String,String> names;
 
   // The reference to the structural objectclass for this name form.
-  private ObjectClass structuralClass;
+  private final ObjectClass structuralClass;
+
+  // The set of optional attribute types for this name form.
+  private final Set<AttributeType> optionalAttributes;
+
+  // The set of required attribute types for this name form.
+  private final Set<AttributeType> requiredAttributes;
+
+  // The definition string used to create this name form.
+  private final String definition;
 
   // The description for this name form.
-  private String description;
+  private final String description;
 
   // The OID for this name form.
-  private String oid;
-
-  // The path to the schema file that contains the definition for this
-  // name form.
-  private String schemaFile;
+  private final String oid;
 
 
 
   /**
    * Creates a new name form definition with the provided information.
    *
+   * @param  definition          The definition string used to create
+   *                             this name form.  It must not be
+   *                             {@code null}.
    * @param  names               The set of names that may be used to
    *                             reference this name form.
-   * @param  oid                 The OID for this name form.
+   * @param  oid                 The OID for this name form.  It must
+   *                             not be {@code null}.
    * @param  description         The description for this name form.
    * @param  isObsolete          Indicates whether this name form is
    *                             declared "obsolete".
    * @param  structuralClass     The structural objectclass with which
-   *                             this name form is associated.
+   *                             this name form is associated.  It
+   *                             must not be {@code null}.
    * @param  requiredAttributes  The set of required attribute types
    *                             for this name form.
    * @param  optionalAttributes  The set of optional attribute types
@@ -106,36 +116,111 @@
    * @param  extraProperties     A set of extra properties for this
    *                             name form.
    */
-  public NameForm(ConcurrentHashMap<String,String> names, String oid,
-              String description, boolean isObsolete,
-              ObjectClass structuralClass,
-              CopyOnWriteArraySet<AttributeType> requiredAttributes,
-              CopyOnWriteArraySet<AttributeType> optionalAttributes,
-              ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-                   extraProperties)
+  public NameForm(String definition, Map<String,String> names,
+                  String oid, String description, boolean isObsolete,
+                  ObjectClass structuralClass,
+                  Set<AttributeType> requiredAttributes,
+                  Set<AttributeType> optionalAttributes,
+                  Map<String,List<String>> extraProperties)
   {
-    assert debugConstructor(CLASS_NAME,
-                            new String[]
-                            {
-                              String.valueOf(names),
-                              String.valueOf(oid),
-                              String.valueOf(description),
-                              String.valueOf(isObsolete),
-                              String.valueOf(structuralClass),
-                              String.valueOf(requiredAttributes),
-                              String.valueOf(optionalAttributes),
-                              String.valueOf(extraProperties)
-                            });
+    assert debugConstructor(CLASS_NAME, String.valueOf(definition),
+                            String.valueOf(names),
+                            String.valueOf(oid),
+                            String.valueOf(description),
+                            String.valueOf(isObsolete),
+                            String.valueOf(structuralClass),
+                            String.valueOf(requiredAttributes),
+                            String.valueOf(optionalAttributes),
+                            String.valueOf(extraProperties));
 
-    this.names              = names;
-    this.oid                = oid;
-    this.description        = description;
-    this.isObsolete         = isObsolete;
-    this.structuralClass    = structuralClass;
-    this.requiredAttributes = requiredAttributes;
-    this.optionalAttributes = optionalAttributes;
-    this.schemaFile         = null;
-    this.extraProperties    = extraProperties;
+    ensureNotNull(definition, oid, structuralClass);
+
+    this.definition      = definition;
+    this.oid             = oid;
+    this.description     = description;
+    this.isObsolete      = isObsolete;
+    this.structuralClass = structuralClass;
+
+    if ((names == null) || names.isEmpty())
+    {
+      this.names = new LinkedHashMap<String,String>(0);
+    }
+    else
+    {
+      this.names = new LinkedHashMap<String,String>(names);
+    }
+
+    if ((requiredAttributes == null) || requiredAttributes.isEmpty())
+    {
+      this.requiredAttributes = new LinkedHashSet<AttributeType>(0);
+    }
+    else
+    {
+      this.requiredAttributes =
+           new LinkedHashSet<AttributeType>(requiredAttributes);
+    }
+
+    if ((optionalAttributes == null) || optionalAttributes.isEmpty())
+    {
+      this.optionalAttributes = new LinkedHashSet<AttributeType>(0);
+    }
+    else
+    {
+      this.optionalAttributes =
+           new LinkedHashSet<AttributeType>(optionalAttributes);
+    }
+
+    if ((extraProperties == null) || extraProperties.isEmpty())
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(0);
+    }
+    else
+    {
+      this.extraProperties =
+           new LinkedHashMap<String,List<String>>(extraProperties);
+    }
+  }
+
+
+
+  /**
+   * Retrieves the definition string used to create this name form.
+   *
+   * @return  The definition string used to create this name form.
+   */
+  public String getDefinition()
+  {
+    assert debugEnter(CLASS_NAME, "getDefinition");
+
+    return definition;
+  }
+
+
+
+  /**
+   * Creates a new instance of this name form based on the definition
+   * string.  It will also preserve other state information associated
+   * with this name form that is not included in the definition string
+   * (e.g., the name of the schema file with which it is associated).
+   *
+   * @return  The new instance of this name form based on the
+   *          definition string.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to create a new name form instance
+   *                              from the definition string.
+   */
+  public NameForm recreateFromDefinition()
+         throws DirectoryException
+  {
+    ByteString value  = ByteStringFactory.create(definition);
+    Schema     schema = DirectoryConfig.getSchema();
+
+    NameForm nf = NameFormSyntax.decodeNameForm(value, schema);
+    nf.setSchemaFile(getSchemaFile());
+
+    return nf;
   }
 
 
@@ -149,7 +234,7 @@
    * @return  The set of names that may be used to reference this
    *          name form.
    */
-  public ConcurrentHashMap<String,String> getNames()
+  public Map<String,String> getNames()
   {
     assert debugEnter(CLASS_NAME, "getNames");
 
@@ -159,33 +244,14 @@
 
 
   /**
-   * Specifies the set of names that may be used to reference this
-   * name form.  The provided set must provide a mapping between each
-   * name in all lowercase characters and that name in a user-defined
-   * form (which may include mixed capitalization).
-   *
-   * @param  names  The set of names that may be used to reference
-   *                this name form.
-   */
-  public void setNames(ConcurrentHashMap<String,String> names)
-  {
-    assert debugEnter(CLASS_NAME, "setNames", String.valueOf(names));
-
-    this.names = names;
-  }
-
-
-
-  /**
    * Indicates whether the provided lowercase name may be used to
    * reference this name form.
    *
    * @param  lowerName  The name for which to make the determination,
    *                    in all lowercase characters.
    *
-   * @return  <CODE>true</CODE> if the provided lowercase name may be
-   *          used to reference this name form, or <CODE>false</CODE>
-   *          if not.
+   * @return  {@code true} if the provided lowercase name may be used
+   *          to reference this name form, or {@code false} if not.
    */
   public boolean hasName(String lowerName)
   {
@@ -198,41 +264,6 @@
 
 
   /**
-   * Adds the provided name to the set of names that may be used to
-   * reference this name form.
-   *
-   * @param  name  The name to add to the set of names that may be
-   *               used to reference this name form.
-   */
-  public void addName(String name)
-  {
-    assert debugEnter(CLASS_NAME, "addName", String.valueOf(name));
-
-    String lowerName = toLowerCase(name);
-    names.put(lowerName, name);
-  }
-
-
-
-  /**
-   * Removes the provided lowercase name from the set of names that
-   * may be used to reference this name form.
-   *
-   * @param  lowerName  The name to remove from the set of names that
-   *                    may be used to reference this name form, in
-   *                    all lowercase characters.
-   */
-  public void removeName(String lowerName)
-  {
-    assert debugEnter(CLASS_NAME, "removeName",
-                      String.valueOf(lowerName));
-
-    names.remove(lowerName);
-  }
-
-
-
-  /**
    * Retrieves the OID for this name form.
    *
    * @return  The OID for this name form.
@@ -247,20 +278,6 @@
 
 
   /**
-   * Specifies the OID for this name form.
-   *
-   * @param  oid  The OID for this name form.
-   */
-  public void setOID(String oid)
-  {
-    assert debugEnter(CLASS_NAME, "setOID", String.valueOf(oid));
-
-    this.oid = oid;
-  }
-
-
-
-  /**
    * Retrieves the name or OID that should be used to reference this
    * name form.  If at least one name is defined, then the first will
    * be returned.  Otherwise, the OID will be returned.
@@ -292,9 +309,9 @@
    * @param  lowerValue  The value, in all lowercase characters, that
    *                     may be used to make the determination.
    *
-   * @return  <CODE>true</CODE> if the provided lowercase value is one
-   *          of the names or the OID of this name form, or
-   *          <CODE>false</CODE> if it is not.
+   * @return  {@code true} if the provided lowercase value is one of
+   *          the names or the OID of this name form, or {@code false}
+   *          if it is not.
    */
   public boolean hasNameOrOID(String lowerValue)
   {
@@ -316,14 +333,21 @@
    * definition for this name form.
    *
    * @return  The path to the schema file that contains the definition
-   *          for this name form, or <CODE>null</CODE> if it is not
-   *          known or if it is not stored in any schema file.
+   *          for this name form, or {@code null} if it is not known
+   *          or if it is not stored in any schema file.
    */
   public String getSchemaFile()
   {
     assert debugEnter(CLASS_NAME, "getSchemaFile");
 
-    return schemaFile;
+    List<String> values =
+         extraProperties.get(SCHEMA_PROPERTY_FILENAME);
+    if ((values == null) || values.isEmpty())
+    {
+      return null;
+    }
+
+    return values.get(0);
   }
 
 
@@ -340,7 +364,7 @@
     assert debugEnter(CLASS_NAME, "setSchemaFile",
                       String.valueOf(schemaFile));
 
-    this.schemaFile = schemaFile;
+    setExtraProperty(SCHEMA_PROPERTY_FILENAME, schemaFile);
   }
 
 
@@ -348,8 +372,8 @@
   /**
    * Retrieves the description for this name form.
    *
-   * @return  The description for this name form, or <CODE>null</CODE>
-   *          if there is none.
+   * @return  The description for this name form, or {@code true} if
+   *          there is none.
    */
   public String getDescription()
   {
@@ -361,21 +385,6 @@
 
 
   /**
-   * Specifies the description for this name form.
-   *
-   * @param  description  The description for this name form.
-   */
-  public void setDescription(String description)
-  {
-    assert debugEnter(CLASS_NAME, "setDescription",
-                      String.valueOf(description));
-
-    this.description = description;
-  }
-
-
-
-  /**
    * Retrieves the reference to the structural objectclass for this
    * name form.
    *
@@ -392,27 +401,11 @@
 
 
   /**
-   * Specifies the structural objectclass for this name form.
-   *
-   * @param  structuralClass  The structural objectclass for this name
-   *                          form.
-   */
-  public void setStructuralClass(ObjectClass structuralClass)
-  {
-    assert debugEnter(CLASS_NAME, "setStructuralClass",
-                      String.valueOf(structuralClass));
-
-    this.structuralClass = structuralClass;
-  }
-
-
-
-  /**
    * Retrieves the set of required attributes for this name form.
    *
    * @return  The set of required attributes for this name form.
    */
-  public CopyOnWriteArraySet<AttributeType> getRequiredAttributes()
+  public Set<AttributeType> getRequiredAttributes()
   {
     assert debugEnter(CLASS_NAME, "getRequiredAttributes");
 
@@ -428,9 +421,8 @@
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          required by this name form, or <CODE>false</CODE> if
-   *          not.
+   * @return  {@code true} if the provided attribute type is required
+   *          by this name form, or {@code false} if not.
    */
   public boolean isRequired(AttributeType attributeType)
   {
@@ -443,62 +435,11 @@
 
 
   /**
-   * Specifies the set of required attributes for this name form.
-   *
-   * @param  requiredAttributes  The set of required attributes for
-   *                             this name form.
-   */
-  public void setRequiredAttributes(CopyOnWriteArraySet<AttributeType>
-                                         requiredAttributes)
-  {
-    assert debugEnter(CLASS_NAME, "setRequiredAttributes",
-                      String.valueOf(requiredAttributes));
-
-    this.requiredAttributes = requiredAttributes;
-  }
-
-
-
-  /**
-   * Adds the provided attribute to the set of required attributes for
-   * this name form.
-   *
-   * @param  attributeType  The attribute type to add to the set of
-   *                        required attributes for this name form.
-   */
-  public void addRequiredAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "addRequiredAttribute",
-                      String.valueOf(attributeType));
-
-    requiredAttributes.add(attributeType);
-  }
-
-
-
-  /**
-   * Removes the provided attribute from the set of required
-   * attributes for this name form.
-   *
-   * @param  attributeType  The attribute type to remove from the set
-   *                        of required attributes for this name form.
-   */
-  public void removeRequiredAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "removeRequiredAttribute",
-                      String.valueOf(attributeType));
-
-    requiredAttributes.remove(attributeType);
-  }
-
-
-
-  /**
    * Retrieves the set of optional attributes for this name form.
    *
    * @return  The set of optional attributes for this name form.
    */
-  public CopyOnWriteArraySet<AttributeType> getOptionalAttributes()
+  public Set<AttributeType> getOptionalAttributes()
   {
     assert debugEnter(CLASS_NAME, "getOptionalAttributes");
 
@@ -514,9 +455,8 @@
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          optional for this name form, or <CODE>false</CODE> if
-   *          not.
+   * @return  {@code true} if the provided attribute type is optional
+   *          for this name form, or {@code false} if not.
    */
   public boolean isOptional(AttributeType attributeType)
   {
@@ -529,66 +469,15 @@
 
 
   /**
-   * Specifies the set of optional attributes for this name form.
-   *
-   * @param  optionalAttributes  The set of optional attributes for
-   *                             this name form.
-   */
-  public void setOptionalAttributes(CopyOnWriteArraySet<AttributeType>
-                                         optionalAttributes)
-  {
-    assert debugEnter(CLASS_NAME, "setOptionalAttributes",
-                      String.valueOf(optionalAttributes));
-
-    this.optionalAttributes = optionalAttributes;
-  }
-
-
-
-  /**
-   * Adds the provided attribute to the set of optional attributes for
-   * this name form.
-   *
-   * @param  attributeType  The attribute type to add to the set of
-   *                        optional attributes for this name form.
-   */
-  public void addOptionalAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "addOptionalAttribute",
-                      String.valueOf(attributeType));
-
-    optionalAttributes.add(attributeType);
-  }
-
-
-
-  /**
-   * Removes the provided attribute from the set of optional
-   * attributes for this name form.
-   *
-   * @param  attributeType  The attribute type to remove from the set
-   *                        optional attributes for this name form.
-   */
-  public void removeOptionalAttribute(AttributeType attributeType)
-  {
-    assert debugEnter(CLASS_NAME, "removeOptionalAttribute",
-                      String.valueOf(attributeType));
-
-    optionalAttributes.remove(attributeType);
-  }
-
-
-
-  /**
    * Indicates whether the provided attribute type is in the list of
    * required or optional attributes for this name form.
    *
    * @param  attributeType  The attribute type for which to make the
    *                        determination.
    *
-   * @return  <CODE>true</CODE> if the provided attribute type is
-   *          required or allowed for this name form, or
-   *          <CODE>false</CODE> if it is not.
+   * @return  {@code true} if the provided attribute type is required
+   *          or optional for this name form, or {@code false} if it
+   *          is not.
    */
   public boolean isRequiredOrOptional(AttributeType attributeType)
   {
@@ -604,8 +493,8 @@
   /**
    * Indicates whether this name form is declared "obsolete".
    *
-   * @return  <CODE>true</CODE> if this name form is declared
-   *          "obsolete", or <CODE>false</CODE> if it is not.
+   * @return  {@code true} if this name form is declared
+   *          "obsolete", or {@code false} if it is not.
    */
   public boolean isObsolete()
   {
@@ -617,33 +506,15 @@
 
 
   /**
-   * Specifies whether this name form is declared "obsolete".
-   *
-   * @param  isObsolete  Specifies whether this name form is declared
-   *                     "obsolete".
-   */
-  public void setObsolete(boolean isObsolete)
-  {
-    assert debugEnter(CLASS_NAME, "setObsolete",
-                      String.valueOf(isObsolete));
-
-    this.isObsolete = isObsolete;
-  }
-
-
-
-  /**
    * Retrieves a mapping between the names of any extra non-standard
    * properties that may be associated with this name form and the
-   * value for that property.  The caller may alter the contents of
-   * this mapping.
+   * value for that property.
    *
    * @return  A mapping between the names of any extra non-standard
    *          properties that may be associated with this name form
    *          and the value for that property.
    */
-  public ConcurrentHashMap<String,CopyOnWriteArrayList<String>>
-              getExtraProperties()
+  public Map<String,List<String>> getExtraProperties()
   {
     assert debugEnter(CLASS_NAME, "getExtraProperties");
 
@@ -660,11 +531,10 @@
    *                       to retrieve the value.
    *
    * @return  The value of the specified "extra" property for this
-   *          name form, or <CODE>null</CODE> if no such property is
+   *          name form, or {@code null} if no such property is
    *          defined.
    */
-  public CopyOnWriteArrayList<String>
-              getExtraProperty(String propertyName)
+  public List<String> getExtraProperty(String propertyName)
   {
     assert debugEnter(CLASS_NAME, "getExtraProperty",
                       String.valueOf(propertyName));
@@ -675,14 +545,72 @@
 
 
   /**
+   * Specifies the provided "extra" property for this name form.
+   *
+   * @param  name   The name for the "extra" property.  It must not be
+   *                {@code null}.
+   * @param  value  The value for the "extra" property, or
+   *                {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, String value)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(value));
+
+    ensureNotNull(name);
+
+    if (value == null)
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> values = new LinkedList<String>();
+      values.add(value);
+
+      extraProperties.put(name, values);
+    }
+  }
+
+
+
+  /**
+   * Specifies the provided "extra" property for this name form.
+   *
+   * @param  name    The name for the "extra" property.  It must not
+   *                 be {@code null}.
+   * @param  values  The set of value for the "extra" property, or
+   *                 {@code null} if the property is to be removed.
+   */
+  public void setExtraProperty(String name, List<String> values)
+  {
+    assert debugEnter(CLASS_NAME, "setExtraProperty",
+                      String.valueOf(name), String.valueOf(values));
+
+    ensureNotNull(name);
+
+    if ((values == null) || values.isEmpty())
+    {
+      extraProperties.remove(name);
+    }
+    else
+    {
+      LinkedList<String> valuesCopy = new LinkedList<String>(values);
+      extraProperties.put(name, valuesCopy);
+    }
+  }
+
+
+
+  /**
    * Indicates whether the provided object is equal to this name form.
    * The object will be considered equal if it is a name form with the
    * same OID as the current name form.
    *
    * @param  o  The object for which to make the determination.
    *
-   * @return  <CODE>true</CODE> if the provided object is equal to
-   *          this name form, or <CODE>false</CODE> if not.
+   * @return  {@code true} if the provided object is equal to this
+   *          name form, or {@code true} if not.
    */
   public boolean equals(Object o)
   {
@@ -861,8 +789,13 @@
     {
       for (String property : extraProperties.keySet())
       {
-        CopyOnWriteArrayList<String> valueList =
-             extraProperties.get(property);
+        if ((! includeFileElement) &&
+            property.equals(SCHEMA_PROPERTY_FILENAME))
+        {
+          continue;
+        }
+
+        List<String> valueList = extraProperties.get(property);
 
         buffer.append(" ");
         buffer.append(property);
@@ -889,16 +822,6 @@
       }
     }
 
-    if (includeFileElement && (schemaFile != null) &&
-        (! extraProperties.containsKey(SCHEMA_PROPERTY_FILENAME)))
-    {
-      buffer.append(" ");
-      buffer.append(SCHEMA_PROPERTY_FILENAME);
-      buffer.append(" '");
-      buffer.append(schemaFile);
-      buffer.append("'");
-    }
-
     buffer.append(" )");
   }
 }
diff --git a/opends/src/server/org/opends/server/types/ObjectClass.java b/opends/src/server/org/opends/server/types/ObjectClass.java
index 8079522..ea7c0c2 100644
--- a/opends/src/server/org/opends/server/types/ObjectClass.java
+++ b/opends/src/server/org/opends/server/types/ObjectClass.java
@@ -22,16 +22,12 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
 
 
-import static org.opends.server.loggers.Debug.debugConstructor;
-import static org.opends.server.loggers.Debug.debugEnter;
-import static org.opends.server.util.ServerConstants.*;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -41,6 +37,13 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.opends.server.schema.ObjectClassSyntax;
+
+import static org.opends.server.loggers.Debug.debugConstructor;
+import static org.opends.server.loggers.Debug.debugEnter;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.Validator.*;
+
 
 
 /**
@@ -58,7 +61,10 @@
  * fields are accessed via their getters or via the
  * {@link #toString()} methods.
  */
-public final class ObjectClass extends CommonSchemaElements {
+public final class ObjectClass
+       extends CommonSchemaElements
+       implements SchemaFileElement
+{
   /**
    * The fully-qualified name of this class for debugging purposes.
    */
@@ -89,6 +95,9 @@
   // contain any attribute.
   private final boolean isExtensibleObject;
 
+  // The definition string used to create this objectclass.
+  private final String definition;
+
 
 
   /**
@@ -100,62 +109,65 @@
    * from the set of <code>names</code> will be used as the primary
    * name.
    *
+   * @param definition
+   *          The definition string used to create this objectclass.
+   *          It must not be {@code null}.
    * @param primaryName
    *          The primary name for this objectclass, or
-   *          <code>null</code> if there is no primary name.
+   *          {@code null} if there is no primary name.
    * @param names
    *          The set of names that may be used to reference this
-   *          objectclass, or <code>null</code> if there are no
-   *          name.
+   *          objectclass.
    * @param oid
-   *          The OID for this objectclass (must not be
-   *          <code>null</code>).
+   *          The OID for this objectclass.  It must not be
+   *          {@code null}.
    * @param description
-   *          The description for this objectclass, or
-   *          <code>null</code> if there is no description.
+   *          The description for this objectclass, or {@code null} if
+   *          there is no description.
    * @param superiorClass
-   *          The superior class for this objectclass, or
-   *          <code>null</code> if there is no superior object
-   *          classes.
+   *          The superior class for this objectclass, or {@code null}
+   *          if there is no superior object class.
    * @param requiredAttributes
    *          The set of required attribute types for this
-   *          objectclass, or <code>null</code> if there are no
-   *          required attribute types.
+   *          objectclass.
    * @param optionalAttributes
    *          The set of optional attribute types for this
-   *          objectclass, or <code>null</code> if there are no
-   *          optional attribute types.
+   *          objectclass.
    * @param objectClassType
    *          The objectclass type for this objectclass, or
-   *          <code>null</code> to default to structural.
+   *          {@code null} to default to structural.
    * @param isObsolete
    *          Indicates whether this objectclass is declared
    *          "obsolete".
    * @param extraProperties
-   *          A set of extra properties for this objectclass, or
-   *          <code>null</code> if there are no extra properties.
-   * @throws NullPointerException
-   *           If the <code>oid</code> is <code>null</code>.
+   *          A set of extra properties for this objectclass.
    */
-  public ObjectClass(String primaryName, Collection<String> names,
-      String oid, String description, ObjectClass superiorClass,
-      Set<AttributeType> requiredAttributes,
-      Set<AttributeType> optionalAttributes,
-      ObjectClassType objectClassType, boolean isObsolete,
-      Map<String, List<String>> extraProperties)
-      throws NullPointerException {
-
+  public ObjectClass(String definition, String primaryName,
+                     Collection<String> names, String oid,
+                     String description, ObjectClass superiorClass,
+                     Set<AttributeType> requiredAttributes,
+                     Set<AttributeType> optionalAttributes,
+                     ObjectClassType objectClassType,
+                     boolean isObsolete,
+                     Map<String, List<String>> extraProperties)
+  {
     super(primaryName, names, oid, description, isObsolete,
         extraProperties);
 
     assert debugConstructor(CLASS_NAME, String.valueOf(primaryName),
-        String.valueOf(names), String.valueOf(oid), String
-            .valueOf(description), String.valueOf(superiorClass),
-        String.valueOf(requiredAttributes), String
-            .valueOf(optionalAttributes), String
-            .valueOf(objectClassType), String.valueOf(isObsolete),
-        String.valueOf(extraProperties));
+                            String.valueOf(names),
+                            String.valueOf(oid),
+                            String.valueOf(description),
+                            String.valueOf(superiorClass),
+                            String.valueOf(requiredAttributes),
+                            String.valueOf(optionalAttributes),
+                            String.valueOf(objectClassType),
+                            String.valueOf(isObsolete),
+                            String.valueOf(extraProperties));
 
+    ensureNotNull(definition, oid);
+
+    this.definition    = definition;
     this.superiorClass = superiorClass;
 
     // Set flag indicating whether or not this object class allows any
@@ -214,6 +226,49 @@
 
 
   /**
+   * Retrieves the definition string used to create this objectclass.
+   *
+   * @return  The definition string used to create this objectclass.
+   */
+  public String getDefinition()
+  {
+    assert debugEnter(CLASS_NAME, "getDefinition");
+
+    return definition;
+  }
+
+
+
+  /**
+   * Creates a new instance of this objectclass based on the
+   * definition string.  It will also preserve other state information
+   * associated with this objectclass that is not included in the
+   * definition string (e.g., the name of the schema file with which
+   * it is associated).
+   *
+   * @return  The new instance of this objectclass based on the
+   *          definition string.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to create a new objectclass instance
+   *                              from the definition string.
+   */
+  public ObjectClass recreateFromDefinition()
+         throws DirectoryException
+  {
+    ByteString value  = ByteStringFactory.create(definition);
+    Schema     schema = DirectoryConfig.getSchema();
+
+    ObjectClass oc = ObjectClassSyntax.decodeObjectClass(value,
+                                                         schema);
+    oc.setSchemaFile(getSchemaFile());
+
+    return oc;
+  }
+
+
+
+  /**
    * Retrieves the reference to the superior class for this
    * objectclass.
    *
diff --git a/opends/src/server/org/opends/server/types/Schema.java b/opends/src/server/org/opends/server/types/Schema.java
index d0ee261..d97b5bd 100644
--- a/opends/src/server/org/opends/server/types/Schema.java
+++ b/opends/src/server/org/opends/server/types/Schema.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
@@ -364,7 +364,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = attributeType.toString();
+      String valueString = attributeType.getDefinition();
       ASN1OctetString rawValue = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -401,7 +401,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = attributeType.toString();
+      String valueString = attributeType.getDefinition();
       ASN1OctetString rawValue = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -556,7 +556,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = objectClass.toString();
+      String valueString = objectClass.getDefinition();
       ASN1OctetString rawValue = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -592,7 +592,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = objectClass.toString();
+      String valueString = objectClass.getDefinition();
       ASN1OctetString rawValue = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -1821,7 +1821,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = matchingRuleUse.toString();
+      String valueString = matchingRuleUse.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -1853,7 +1853,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = matchingRuleUse.toString();
+      String valueString = matchingRuleUse.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -1989,7 +1989,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = ditContentRule.toString();
+      String valueString = ditContentRule.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -2021,7 +2021,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = ditContentRule.toString();
+      String valueString = ditContentRule.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -2234,7 +2234,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = ditStructureRule.toString();
+      String valueString = ditStructureRule.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -2269,7 +2269,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = ditStructureRule.toString();
+      String valueString = ditStructureRule.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -2496,7 +2496,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = nameForm.toString();
+      String valueString = nameForm.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -2531,7 +2531,7 @@
       // rather than the attribute type because otherwise it would use
       // a very expensive matching rule (OID first component match)
       // that would kill performance.
-      String valueString = nameForm.toString();
+      String valueString = nameForm.getDefinition();
       ASN1OctetString rawValue  = new ASN1OctetString(valueString);
       ASN1OctetString normValue =
            new ASN1OctetString(toLowerCase(valueString));
@@ -2542,6 +2542,244 @@
 
 
   /**
+   * Recursively rebuilds all schema elements that are dependent upon
+   * the provided element.  This must be invoked whenever an existing
+   * schema element is modified in order to ensure that any elements
+   * that depend on it should also be recreated to reflect the change.
+   * <BR><BR>
+   * The following conditions create dependencies between schema
+   * elements:
+   * <UL>
+   *   <LI>If an attribute type references a superior attribute type,
+   *       then it is dependent upon that superior attribute
+   *       type.</LI>
+   *   <LI>If an objectclass requires or allows an attribute type,
+   *       then it is dependent upon that attribute type.</LI>
+   *   <LI>If a name form requires or allows an attribute type in the
+   *       RDN, then it is dependent upon that attribute type.</LI>
+   *   <LI>If a DIT content rule requires, allows, or forbids the use
+   *       of an attribute type, then it is dependent upon that
+   *       attribute type.</LI>
+   *   <LI>If a matching rule use references an attribute type, then
+   *       it is dependent upon that attribute type.</LI>
+   *   <LI>If an objectclass references a superior objectclass, then
+   *       it is dependent upon that superior objectclass.</LI>
+   *   <LI>If a name form references a structural objectclass, then it
+   *       is dependent upon that objectclass.</LI>
+   *   <LI>If a DIT content rule references a structural or auxiliary
+   *       objectclass, then it is dependent upon that
+   *       objectclass.</LI>
+   *   <LI>If a DIT structure rule references a name form, then it is
+   *       dependent upon that name form.</LI>
+   *   <LI>If a DIT structure rule references a superior DIT structure
+   *       rule, then it is dependent upon that superior DIT structure
+   *       rule.</LI>
+   * </UL>
+   *
+   * @param  element  The element for which to recursively rebuild all
+   *                  dependent elements.
+   *
+   * @throws  DirectoryException  If a problem occurs while rebuilding
+   *                              any of the schema elements.
+   */
+  public final void rebuildDependentElements(
+                         SchemaFileElement element)
+         throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "rebuildDependentElements",
+                      String.valueOf(element));
+
+    try
+    {
+      rebuildDependentElements(element, 0);
+    }
+    catch (DirectoryException de)
+    {
+      // If we got an error as a result of a circular reference, then
+      // we want to make sure that the schema element we call out is
+      // the one that is at the root of the problem.
+      if (de.getErrorMessageID() ==
+          MSGID_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE)
+      {
+        int    msgID   = MSGID_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE;
+        String message = getMessage(msgID, element.getDefinition());
+        throw new DirectoryException(de.getResultCode(), message,
+                                     msgID, de);
+      }
+
+
+      // It wasn't a circular reference error, so just re-throw the
+      // exception.
+      throw de;
+    }
+  }
+
+
+
+  /**
+   * Recursively rebuilds all schema elements that are dependent upon
+   * the provided element, increasing the depth for each level of
+   * recursion to protect against errors due to circular references.
+   *
+   * @param  element  The element for which to recursively rebuild all
+   *                  dependent elements.
+   * @param  depth    The current recursion depth.
+   *
+   * @throws  DirectoryException  If a problem occurs while rebuilding
+   *                              any of the schema elements.
+   */
+  private final void rebuildDependentElements(
+                          SchemaFileElement element, int depth)
+          throws DirectoryException
+  {
+    assert debugEnter(CLASS_NAME, "rebuildDependentElements",
+                      String.valueOf(element), String.valueOf(depth));
+
+    if (depth > 20)
+    {
+      // FIXME -- Is this an appropriate maximum depth for detecting
+      // circular references?
+      int    msgID   = MSGID_SCHEMA_CIRCULAR_DEPENDENCY_REFERENCE;
+      String message = getMessage(msgID, element.getDefinition());
+      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                   message, msgID);
+    }
+
+
+    // Figure out what type of element we're dealing with and make the
+    // appropriate determinations for that element.
+    if (element instanceof AttributeType)
+    {
+      AttributeType t = (AttributeType) element;
+
+      for (AttributeType at : attributeTypes.values())
+      {
+        if ((at.getSuperiorType() != null) &&
+            at.getSuperiorType().equals(t))
+        {
+          AttributeType newAT = at.recreateFromDefinition();
+          deregisterAttributeType(at);
+          registerAttributeType(newAT, true);
+          rebuildDependentElements(at, depth+1);
+        }
+      }
+
+      for (ObjectClass oc : objectClasses.values())
+      {
+        if (oc.getRequiredAttributes().contains(t) ||
+            oc.getOptionalAttributes().contains(t))
+        {
+          ObjectClass newOC = oc.recreateFromDefinition();
+          deregisterObjectClass(oc);
+          registerObjectClass(newOC, true);
+          rebuildDependentElements(oc, depth+1);
+        }
+      }
+
+      for (NameForm nf : nameFormsByOC.values())
+      {
+        if (nf.getRequiredAttributes().contains(t) ||
+            nf.getOptionalAttributes().contains(t))
+        {
+          NameForm newNF = nf.recreateFromDefinition();
+          deregisterNameForm(nf);
+          registerNameForm(newNF, true);
+          rebuildDependentElements(nf, depth+1);
+        }
+      }
+
+      for (DITContentRule dcr : ditContentRules.values())
+      {
+        if (dcr.getRequiredAttributes().contains(t) ||
+            dcr.getOptionalAttributes().contains(t) ||
+            dcr.getProhibitedAttributes().contains(t))
+        {
+          DITContentRule newDCR = dcr.recreateFromDefinition();
+          deregisterDITContentRule(dcr);
+          registerDITContentRule(newDCR, true);
+          rebuildDependentElements(dcr, depth+1);
+        }
+      }
+
+      for (MatchingRuleUse mru : matchingRuleUses.values())
+      {
+        if (mru.getAttributes().contains(t))
+        {
+          MatchingRuleUse newMRU = mru.recreateFromDefinition();
+          deregisterMatchingRuleUse(mru);
+          registerMatchingRuleUse(newMRU, true);
+          rebuildDependentElements(mru, depth+1);
+        }
+      }
+    }
+    else if (element instanceof ObjectClass)
+    {
+      ObjectClass c = (ObjectClass) element;
+
+      for (ObjectClass oc : objectClasses.values())
+      {
+        if ((oc.getSuperiorClass() != null) &&
+            oc.getSuperiorClass().equals(c))
+        {
+          ObjectClass newOC = oc.recreateFromDefinition();
+          deregisterObjectClass(oc);
+          registerObjectClass(newOC, true);
+          rebuildDependentElements(oc, depth+1);
+        }
+      }
+
+      NameForm nf = nameFormsByOC.get(c);
+      if (nf != null)
+      {
+        NameForm newNF = nf.recreateFromDefinition();
+        deregisterNameForm(nf);
+        registerNameForm(newNF, true);
+        rebuildDependentElements(nf, depth+1);
+      }
+
+      for (DITContentRule dcr : ditContentRules.values())
+      {
+        if (dcr.getStructuralClass().equals(c) ||
+            dcr.getAuxiliaryClasses().contains(c))
+        {
+          DITContentRule newDCR = dcr.recreateFromDefinition();
+          deregisterDITContentRule(dcr);
+          registerDITContentRule(newDCR, true);
+          rebuildDependentElements(dcr, depth+1);
+        }
+      }
+    }
+    else if (element instanceof NameForm)
+    {
+      NameForm n = (NameForm) element;
+      DITStructureRule dsr = ditStructureRulesByNameForm.get(n);
+      if (dsr != null)
+      {
+        DITStructureRule newDSR = dsr.recreateFromDefinition();
+        deregisterDITStructureRule(dsr);
+        registerDITStructureRule(newDSR, true);
+        rebuildDependentElements(dsr, depth+1);
+      }
+    }
+    else if (element instanceof DITStructureRule)
+    {
+      DITStructureRule d = (DITStructureRule) element;
+      for (DITStructureRule dsr : ditStructureRulesByID.values())
+      {
+        if (dsr.getSuperiorRules().contains(d))
+        {
+          DITStructureRule newDSR = dsr.recreateFromDefinition();
+          deregisterDITStructureRule(dsr);
+          registerDITStructureRule(newDSR, true);
+          rebuildDependentElements(dsr, depth+1);
+        }
+      }
+    }
+  }
+
+
+
+  /**
    * Creates a new <CODE>Schema</CODE> object that is a duplicate of
    * this one.  It elements may be added and removed from the
    * duplicate without impacting this version.
diff --git a/opends/src/server/org/opends/server/types/SchemaFileElement.java b/opends/src/server/org/opends/server/types/SchemaFileElement.java
new file mode 100644
index 0000000..ca1ca95
--- /dev/null
+++ b/opends/src/server/org/opends/server/types/SchemaFileElement.java
@@ -0,0 +1,107 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.types;
+
+
+
+/**
+ * This interface defines a set of methods that must be provided by a
+ * schema file element, which is a schema element that is loaded from
+ * a schema configuration file.
+ * <BR><BR>
+ * Note that this interface is not meant to be implemented by
+ * third-party code, and only the following classes should be
+ * considered schema file elements:
+ * <UL>
+ *   <LI>{@code org.opends.server.types.AttributeType}</LI>
+ *   <LI>{@code org.opends.server.types.ObjectClass}</LI>
+ *   <LI>{@code org.opends.server.types.NameForm}</LI>
+ *   <LI>{@code org.opends.server.types.DITContentRule}</LI>
+ *   <LI>{@code org.opends.server.types.DITStructureRule}</LI>
+ *   <LI>{@code org.opends.server.types.MatchingRuleUse}</LI>
+ * </UL>
+ */
+public interface SchemaFileElement
+{
+  /**
+   * Retrieves the name of the schema file in which this element is
+   * defined.
+   *
+   * @return  The name of the schema file in which this element is
+   *          defined, or {@code null} if it is not known or this
+   *          element is not defined in any schema file.
+   */
+  public String getSchemaFile();
+
+
+
+  /**
+   * Specifies the name of the schema file in which this element is
+   * defined.
+   *
+   * @param  schemaFile  The name of the schema file in which this
+   *                     element is defined, or {@code null} if it is
+   *                     not defined in any schema file.
+   */
+  public void setSchemaFile(String schemaFile);
+
+
+
+  /**
+   * Retrieves the definition string that is used to represent this
+   * element in the schema configuration file.
+   *
+   * @return  The definition string that should be used to represent
+   *          this element in the schema configuration file.
+   */
+  public String getDefinition();
+
+
+
+  /**
+   * Creates a new instance of this schema element based on the
+   * definition from the schema file.  The new instance should also
+   * be created with all appropriate state information that may not
+   * be directly represented in the schema definition (e.g., the name
+   * of the schema file containing the definition).
+   * <BR><BR>
+   * Whenever an existing schema file element is modified with the
+   * server online, this method will be invoked to recreate any
+   * schema elements that might have been dependent upon the
+   * modified element.
+   *
+   * @return  A new instance of this schema element based on the
+   *          definition.
+   *
+   * @throws  DirectoryException  If a problem occurs while attempting
+   *                              to create the new instance of this
+   *                              schema element.
+   */
+  public SchemaFileElement recreateFromDefinition()
+         throws DirectoryException;
+}
+
diff --git a/opends/src/server/org/opends/server/util/ServerConstants.java b/opends/src/server/org/opends/server/util/ServerConstants.java
index d3035d1..5db76e1 100644
--- a/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opends/src/server/org/opends/server/util/ServerConstants.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.util;
 
@@ -1396,6 +1396,55 @@
 
 
   /**
+   * The description for the alert type that will be used for the alert
+   * notification generated if a problem occurs while creating copies of the
+   * existing schema configuration files and a problem occurs that leaves the
+   * schema configuration in a potentially inconsistent state.
+   */
+  public static final String ALERT_DESCRIPTION_CANNOT_COPY_SCHEMA_FILES =
+      "This alert type will be used to notify administrators if a problem " +
+      "occurs while attempting to create copies of the existing schema " +
+      "configuration files before making a schema update, and the schema " +
+      "configuration is left in a potentially inconsistent state.";
+
+
+
+  /**
+   * The alert type string that will be used for the alert notification
+   * generated if a problem occurs while creating copies of the existing schema
+   * files in a manner that may leave the schema configuration inconsistent.
+   */
+  public static final String ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES =
+       "org.opends.server.CannotCopySchemaFiles";
+
+
+
+  /**
+   * The description for the alert type that will be used for the alert
+   * notification generated if a problem occurs while writing new versions of
+   * the server schema configuration files and a problem occurs that leaves the
+   * schema configuration in a potentially inconsistent state.
+   */
+  public static final String ALERT_DESCRIPTION_CANNOT_WRITE_NEW_SCHEMA_FILES =
+      "This alert type will be used to notify administrators if a problem " +
+      "occurs while attempting to write new verisons of the server schema " +
+      "configuration files, and the schema configuration is left in a " +
+      "potentially inconsistent state.";
+
+
+
+  /**
+   * The alert type string that will be used for the alert notification
+   * generated if a problem occurs while writing new versions of the server
+   * schema files in a manner that may leave the schema configuration
+   * inconsistent.
+   */
+  public static final String ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES =
+       "org.opends.server.CannotWriteNewSchemaFiles";
+
+
+
+  /**
    * The name of the default password storage scheme that will be used for new
    * passwords.
    */
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java
index 67c1e2f..1996d52 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaBackendTestCase.java
@@ -22,12 +22,14 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.backends;
 
 
 
+import java.io.File;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 
@@ -36,11 +38,14 @@
 import org.testng.annotations.Test;
 
 import org.opends.server.TestCaseUtils;
+import org.opends.server.config.ConfigEntry;
+import org.opends.server.config.ConfigException;
 import org.opends.server.core.AddOperation;
 import org.opends.server.core.DeleteOperation;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyOperation;
 import org.opends.server.core.ModifyDNOperation;
+import org.opends.server.core.SchemaConfigManager;
 import org.opends.server.protocols.internal.InternalClientConnection;
 import org.opends.server.protocols.internal.InternalSearchOperation;
 import org.opends.server.tools.LDAPModify;
@@ -49,10 +54,17 @@
 import org.opends.server.types.AttributeValue;
 import org.opends.server.types.ByteStringFactory;
 import org.opends.server.types.DirectoryException;
+import org.opends.server.types.DITContentRule;
 import org.opends.server.types.DN;
 import org.opends.server.types.Entry;
+import org.opends.server.types.ExistingFileBehavior;
+import org.opends.server.types.InitializationException;
+import org.opends.server.types.LDIFExportConfig;
+import org.opends.server.types.LDIFImportConfig;
+import org.opends.server.types.MatchingRuleUse;
 import org.opends.server.types.Modification;
 import org.opends.server.types.ModificationType;
+import org.opends.server.types.ObjectClass;
 import org.opends.server.types.RDN;
 import org.opends.server.types.ResultCode;
 import org.opends.server.types.SearchFilter;
@@ -94,6 +106,23 @@
 
 
   /**
+   * Tests the {@code initializeBackend} method by providing a null
+   * configuration entry.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(expectedExceptions = { ConfigException.class,
+                               InitializationException.class })
+  public void testInitializeWithNullEntry()
+         throws Exception
+  {
+    SchemaBackend schemaBackend = new SchemaBackend();
+    schemaBackend.initializeBackend(null, new DN[0]);
+  }
+
+
+
+  /**
    * Tests the {@code isLocal} method to ensure that it is considered local.
    */
   @Test()
@@ -443,6 +472,127 @@
 
   /**
    * Tests the behavior of the schema backend when attempting to add a new
+   * attribute type that is not allowed to be altered.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddUnsupportedAttr()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClass",
+         "objectClass: extensibleObject");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is not allowed to be altered.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveUnsupportedAttr()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: objectClass",
+         "objectClass: subschema",
+         "-",
+         "add: objectClass",
+         "objectClass: extensibleObject");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove all
+   * attribute type definitions.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAllAttributeTypes()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to replace all
+   * attribute types.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testReplaceAllAttributeTypes()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "replace: attributeTypes");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
    * attribute type with a valid syntax and that isn't already defined.
    *
    * @throws  Exception  If an unexpected problem occurs.
@@ -455,10 +605,10 @@
          "dn: cn=schema",
          "changetype: modify",
          "add: attributeTypes",
-         "attributeTypes: ( 1.3.6.1.4.1.26027.1.999.4 NAME " +
-              "'testAddAttributeTypeSuccessful' SYNTAX " +
-              "1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN " +
-              "'SchemaBackendTestCase' )");
+         "attributeTypes: ( 1.3.6.1.4.1.26027.1.999.4 " +
+              "NAME 'testAddAttributeTypeSuccessful' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORGIN 'SchemaBackendTestCase' )");
 
     String attrName = "testaddattributetypesuccessful";
     assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
@@ -493,10 +643,10 @@
          "dn: cn=schema",
          "changetype: modify",
          "add: attributeTypes",
-         "attributeTypes: ( testaddattributetypesuccessfulnooid-oid NAME " +
-              "'testAddAttributeTypeSuccessfulNoOID' SYNTAX " +
-              "1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN " +
-              "'SchemaBackendTestCase' )");
+         "attributeTypes: ( testaddattributetypesuccessfulnooid-oid " +
+              "NAME 'testAddAttributeTypeSuccessfulNoOID' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORGIN 'SchemaBackendTestCase' )");
 
     String attrName = "testaddattributetypesuccessfulnooid";
     assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
@@ -518,6 +668,143 @@
 
   /**
    * Tests the behavior of the schema backend when attempting to add a new
+   * attribute type to a specific schema file.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void testAddAttributeTypeToAltSchemaFile()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testaddattributetypetoaltschemafile-oid " +
+              "NAME 'testAddAttributeTypeToAltSchemaFile' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' " +
+              "X-SCHEMA-FILE '98-schema-test-attrtype.ldif' )");
+
+    String attrName = "testaddattributetypetoaltschemafile";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    File schemaFile = new File(SchemaConfigManager.getSchemaDirectoryPath(),
+                               "98-schema-test-attrtype.ldif");
+    assertFalse(schemaFile.exists());
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+    assertTrue(schemaFile.exists());
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * attribute type in a manner that replaces an existing definition.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddAttributeTypeSuccessfulReplace()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testaddattributetypesuccessfulreplace-oid " +
+              "NAME 'testAddAttributeTypeSuccessfulReplace' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testaddattributetypesuccessfulreplace-oid " +
+              "NAME 'testAddAttributeTypeSuccessfulReplace' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String attrName = "testaddattributetypesuccessfulreplace";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to replace an
+   * attribute type definition in a custom schema file.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public void testReplaceAttributeTypeInAltSchemaFile()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testreplaceattributetypeinaltschemafile-oid " +
+              "NAME 'testReplaceAttributeTypeInAltSchemaFile' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' " +
+              "X-SCHEMA-FILE '98-schema-test-replaceattrtype.ldif' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testreplaceattributetypeinaltschemafile-oid " +
+              "NAME 'testReplaceAttributeTypeInAltSchemaFile' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String attrName = "testreplaceattributetypeinaltschemafile";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    File schemaFile = new File(SchemaConfigManager.getSchemaDirectoryPath(),
+                               "98-schema-test-replaceattrtype.ldif");
+    assertFalse(schemaFile.exists());
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+    assertTrue(schemaFile.exists());
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
    * attribute type definition that can't be parsed.
    *
    * @throws  Exception  If an unexpected problem occurs.
@@ -548,6 +835,456 @@
 
   /**
    * Tests the behavior of the schema backend when attempting to add a new
+   * attribute type that conflicts with multiple existing types.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddAttributeTypeMultipleConflicts()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testaddattributetypemultipleconflicts-oid NAME " +
+              "( 'testAddAttributeTypeMultipleConflicts' 'cn' 'uid' ) SYNTAX " +
+              "1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN " +
+              "'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * attribute type that references an undefined superior attribute type.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddAttributeTypeUndefinedSuperior()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testaddattributetypeundefinedsuperior-oid NAME " +
+              "'testAddAttributeTypeUndefinedSuperior' SUP undefined SYNTAX " +
+              "1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN " +
+              "'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is defined in the server schema and does not have any
+   * dependencies.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAttributeTypeSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( 1.3.6.1.4.1.26027.1.999.6 NAME " +
+              "'testRemoveAttributeTypeSuccessful' SYNTAX " +
+              "1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN " +
+              "'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( 1.3.6.1.4.1.26027.1.999.6 NAME " +
+              "'testRemoveAttributeTypeSuccessful' SYNTAX " +
+              "1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN " +
+              "'SchemaBackendTestCase' )");
+
+    String attrName = "testremoveattributetypesuccessful";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type and add it back in the same modification.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveThenAddAttributeTypeSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testremovethenaddattributetypesuccessful-oid " +
+              "NAME 'testRemoveThenAddAttributeTypeSuccessful' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( testremovethenaddattributetypesuccessful-oid " +
+              "NAME 'testRemoveThenAddAttributeTypeSuccessful' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: attributeTypes",
+         "attributeTypes: ( testremovethenaddattributetypesuccessful-oid " +
+              "NAME 'testRemoveThenAddAttributeTypeSuccessful' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String attrName = "testremoveattributetypesuccessful";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is not defined in the server schema.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAttributeTypeUndefined()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( testremoveattributetypeundefined-oid " +
+              "NAME 'testRemoveAttributeTypeUndefined' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String attrName = "testremoveattributetypeundefined";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is referenced as the superior type for another
+   * attribute type.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveSuperiorAttributeType()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( 2.5.4.41 NAME 'name' EQUALITY caseIgnoreMatch " +
+              "SUBSTR caseIgnoreSubstringsMatch " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{32768} " +
+              "X-ORIGIN 'RFC 2256' )");
+
+    String attrName = "name";
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is referenced by an existing objectclass.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAttributeTypeReferencedByObjectClass()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( 0.9.2342.19200300.100.1.1 NAME 'uid' " +
+              "EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} " +
+              "X-ORIGIN 'RFC 1274' )");
+
+    String attrName = "uid";
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is referenced by an existing name form.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAttributeTypeReferencedByNameForm()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testremoveattributetypereferencedbynf-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByNF' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: objectClasses",
+         "objectClasses:  ( testremoveattributetypereferencedbynfoc-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByNFOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremoveattributetypereferencedbynfnf-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByNFNF' " +
+              "OC testRemoveAttributeTypeReferencedByNFOC " +
+              "MUST testRemoveAttributeTypeReferencedByNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( testremoveattributetypereferencedbynf-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByNF' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String attrName = "testremoveattributetypereferencedbynf";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is referenced by an existing DIT content rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAttributeTypeReferencedByDCR()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testremoveattributetypereferencedbydcr-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByDCR' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: objectClasses",
+         "objectClasses:  ( testremoveattributetypereferencedbydcroc-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByDCROC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testremoveattributetypereferencedbydcroc-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByDCRDCR' " +
+              "MAY testRemoveAttributeTypeReferencedByDCR " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( testremoveattributetypereferencedbydcr-oid " +
+              "NAME 'testRemoveAttributeTypeReferencedByDCR' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String attrName = "testremoveattributetypereferencedbydcr";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * attribute type that is referenced by an existing matching rule use.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAttributeTypeReferencedByMRU()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testRemoveATRefByMRUMatch",
+                                    "1.3.6.1.4.1.26027.1.999.17");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: attributeTypes",
+         "attributeTypes: ( testremoveatrefbymruat-oid " +
+              "NAME 'testRemoveATRefByMRUAT' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.17 " +
+              "NAME 'testRemoveATRefByMRUMRU' APPLIES testRemoveATRefByMRUAT " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: attributeTypes",
+         "attributeTypes: ( testremoveatrefbymruat-oid " +
+              "NAME 'testRemoveATRefByMRUAT' " +
+              "SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String attrName = "testremoveatrefbymruat";
+    assertFalse(DirectoryServer.getSchema().hasAttributeType(attrName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+
+    MatchingRuleUse mru =
+         DirectoryServer.getSchema().getMatchingRuleUse(matchingRule);
+    assertNotNull(mru);
+    assertTrue(mru.hasName("testremoveatrefbymrumru"));
+
+    assertTrue(DirectoryServer.getSchema().hasAttributeType(attrName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
    * objectclass that doesn't already exist, that has a valid superior class,
    * and for which all attributes contained in it are already defined.
    *
@@ -623,6 +1360,178 @@
 
   /**
    * Tests the behavior of the schema backend when attempting to add a new
+   * objectclass to a specific schema file.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddObjectClassToAltSchemaFile()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses: ( testaddobjectclasstoaltschemafile-oid NAME " +
+              "'testAddObjectClassToAltSchemaFile' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase' " +
+              "X-SCHEMA-FILE '98-schema-test-oc.ldif' )");
+
+    String ocName = "testaddobjectclasstoaltschemafile";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    File schemaFile = new File(SchemaConfigManager.getSchemaDirectoryPath(),
+                               "98-schema-test-oc.ldif");
+    assertFalse(schemaFile.exists());
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasObjectClass(ocName));
+    assertTrue(schemaFile.exists());
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * objectclass that already exists (i.e., a replace)
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddObjectClassSuccessfulReplace()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses: ( testaddobjectclasssuccessfulreplace-oid " +
+              "NAME 'testAddObjectClassSuccessfulReplace' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses: ( testaddobjectclasssuccessfulreplace-oid " +
+              "NAME 'testAddObjectClassSuccessfulReplace' SUP top STRUCTURAL " +
+              "MUST cn MAY description X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String ocName = "testaddobjectclasssuccessfulreplace";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasObjectClass(ocName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * objectclass that conflicts with multiple existing objectclasses.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddObjectClassMultipleConflicts()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses: ( testaddobjectclassmultipleconflicts-oid " +
+              "NAME ( 'testAddObjectClassMultipleConflicts' 'person' " +
+              "'device' ) SUP top STRUCTURAL MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String ocName = "testaddobjectclassmultipleconflicts";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing objectclass definition and then add it back in the same operation
+   * with a different definition.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveThenAddAddObjectClassSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses: ( testremovethenaddobjectclasssuccessful-oid " +
+              "NAME 'testRemoveThenAddObjectClassSuccessful' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: objectClasses",
+         "objectClasses: ( testremovethenaddobjectclasssuccessful-oid " +
+              "NAME 'testRemoveThenAddObjectClassSuccessful' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: objectClasses",
+         "objectClasses: ( testremovethenaddobjectclasssuccessful-oid " +
+              "NAME 'testRemoveThenAddObjectClassSuccessful' SUP top " +
+              "STRUCTURAL MUST cn MAY description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String ocName = "testremovethenaddobjectclasssuccessful";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasObjectClass(ocName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
    * objectclass definition that can't be parsed.
    *
    * @throws  Exception  If an unexpected problem occurs.
@@ -744,5 +1653,2119 @@
 
     assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
   }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * objectclass that exists and for which there are no dependencies.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveObjectClassSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses: ( 1.3.6.1.4.1.26027.1.999.7 NAME " +
+              "'testRemoveObjectClassSuccessful' SUP top STRUCTURAL MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: objectClasses",
+         "objectClasses: ( 1.3.6.1.4.1.26027.1.999.7 NAME " +
+              "'testRemoveObjectClassSuccessful' SUP top STRUCTURAL MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String ocName = "testremoveobjectclasssuccessful";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * objectclass that is the superior class for another objectclass.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveSuperiorObjectClass()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: objectClasses",
+         "objectClasses: ( 2.5.6.6 NAME 'person' SUP top STRUCTURAL " +
+              "MUST ( sn $ cn ) MAY ( userPassword $ telephoneNumber $ " +
+              "seeAlso $ description ) X-ORIGIN 'RFC 2256' )");
+
+    String ocName = "person";
+    assertTrue(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasObjectClass(ocName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * objectclass that is referenced by an existing name form.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveObjectClassReferencedByNameForm()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremoveobjectclassreferencedbynf-oid " +
+              "NAME 'testRemoveObjectClassReferencedByNF' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremoveattributetypereferencedbynfnf-oid " +
+              "NAME 'testRemoveObjectClassReferencedByNFNF' " +
+              "OC testRemoveObjectClassReferencedByNF MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: objectClasses",
+         "objectClasses:  ( testremoveobjectclassreferencedbynf-oid " +
+              "NAME 'testRemoveObjectClassReferencedByNF' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')");
+
+    String ocName = "testremoveobjectclassreferencedbynf";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasObjectClass(ocName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * objectclass that is referenced by an existing DIT content rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveObjectClassReferencedByDCR()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremoveobjectclassreferencedbydcr-oid " +
+              "NAME 'testRemoveObjectClassReferencedByDCR' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testremoveobjectclassreferencedbydcr-oid " +
+              "NAME 'testRemoveObjectClassReferencedByDCRDCR' " +
+              "MAY description X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: objectClasses",
+         "objectClasses:  ( testremoveobjectclassreferencedbydcr-oid " +
+              "NAME 'testRemoveObjectClassReferencedByDCR' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')");
+
+    String ocName = "testremoveobjectclassreferencedbydcr";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasObjectClass(ocName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new name
+   * form that doesn't already exist.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddNameFormSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddnameformsuccessfuloc-oid " +
+              "NAME 'testAddNameFormSuccessfulOC' SUP top STRUCTURAL MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( 1.3.6.1.4.1.26027.1.999.8 " +
+              "NAME 'testAddNameFormSuccessful' " +
+              "OC testAddNameFormSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testaddnameformsuccessful";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new name
+   * form that doesn't already exist to an alternate schema file.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddNameFormToAltSchemaFile()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddnameformtoaltschemafileoc-oid " +
+              "NAME 'testAddNameFormToAltSchemaFileOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testaddnameformtoaltschemafile-oid " +
+              "NAME 'testAddNameFormToAltSchemaFile' " +
+              "OC testAddNameFormToAltSchemaFileOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' " +
+              "X-SCHEMA-FILE '98-schema-test-nameform.ldif' )");
+
+    String nameFormName = "testaddnameformtoaltschemafile";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    File schemaFile = new File(SchemaConfigManager.getSchemaDirectoryPath(),
+                               "98-schema-test-nameform.ldif");
+    assertFalse(schemaFile.exists());
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasNameForm(nameFormName));
+    assertTrue(schemaFile.exists());
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new name
+   * form that references a required attribute type not defined in the server
+   * schema.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddNameFormWithUndefinedReqAT()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddnameformwithundefinedreqatoc-oid " +
+              "NAME 'testAddNameFormWithUndefinedReqATOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testaddnameformwithundefinereqdat-oid " +
+              "NAME 'testAddNameFormWithUndefinedReqAT' " +
+              "OC testAddNameFormWithUndefinedReqATOC MUST xxxundefinedxxx " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testaddnameformwithundefinedreqat";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new name
+   * form that references an optional attribute type not defined in the server
+   * schema.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddNameFormWithUndefinedOptAT()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddnameformwithundefinedoptatoc-oid " +
+              "NAME 'testAddNameFormWithUndefinedOptATOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testaddnameformwithundefineoptdat-oid " +
+              "NAME 'testAddNameFormWithUndefinedOptAT' " +
+              "OC testAddNameFormWithUndefinedOptATOC MUST cn " +
+              "MAY xxxundefinedxxx X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testaddnameformwithundefinedoptat";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new name
+   * form whose structural objectclass is not defined in the server schema.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddNameFormWithUndefinedOC()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: nameForms",
+         "nameForms: ( testaddnameformwithundefinedoc-oid " +
+              "NAME 'testAddNameFormWithUndefinedOC' " +
+              "OC xxxundefinedxxx MUST cn X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testaddnameformwithundefinedoc";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new name
+   * form whose objectclass auxiliary rather than structural.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddNameFormWithAuxiliaryOC()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddnameformwithauxiliaryococ-oid " +
+              "NAME 'testAddNameFormWithAuxiliaryOCOC' SUP top AUXILIARY " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testaddnameformwithauxiliaryoc-oid " +
+              "NAME 'testAddNameFormWithAuxiliaryOC' " +
+              "OC testAddNameFormWithAuxiliaryOCOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testaddnameformwithauxiliaryoc";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new name
+   * form that references a structural objectclass already referenced by another
+   * name form.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddNameFormOCConflict()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddnameformocconflictoc-oid " +
+              "NAME 'testAddNameFormOCConflictOC' SUP top STRUCTURAL MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testaddnameformocconflict-oid " +
+              "NAME 'testAddNameFormOCConflict' " +
+              "OC testAddNameFormOCConflictOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: nameForms",
+         "nameForms: ( testaddnameformocconflict2-oid " +
+              "NAME 'testAddNameFormOCConflict2' " +
+              "OC testAddNameFormOCConflictOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testaddnameformocconflict2";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing name form.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveNameFormSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremovenameformsuccessfuloc-oid " +
+              "NAME 'testRemoveNameFormSuccessfulOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( 1.3.6.1.4.1.26027.1.999.9 " +
+              "NAME 'testRemoveNameFormSuccessful' " +
+              "OC testRemoveNameFormSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: nameForms",
+         "nameForms: ( 1.3.6.1.4.1.26027.1.999.9 " +
+              "NAME 'testRemoveNameFormSuccessful' " +
+              "OC testRemoveNameFormSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testremovenameformsuccessful";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing name form and then add it back in the same operation.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveThenAddNameFormSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremovethenaddnameformsuccessfuloc-oid " +
+              "NAME 'testRemoveThenAddNameFormSuccessfulOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremovethenaddnameformsuccessful-oid " +
+              "NAME 'testRemoveThenAddNameFormSuccessful' " +
+              "OC testRemoveThenAddNameFormSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: nameForms",
+         "nameForms: ( testremovethenaddnameformsuccessful-oid " +
+              "NAME 'testRemoveThenAddNameFormSuccessful' " +
+              "OC testRemoveThenAddNameFormSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremovethenaddnameformsuccessful-oid " +
+              "NAME 'testRemoveThenAddNameFormSuccessful' " +
+              "OC testRemoveThenAddNameFormSuccessfulOC MUST cn MAY sn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String nameFormName = "testremovethenaddnameformsuccessful";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove a name
+   * form that is referenced by a DIT structure rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveNameFormReferencedByDSR()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremovenameformreferencedbydsroc-oid " +
+              "NAME 'testRemoveNameFormReferencedByDSROC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremovenameformreferencedbydsrnf-oid " +
+              "NAME 'testRemoveNameFormReferencedByDSRNF' " +
+              "OC testRemoveNameFormReferencedByDSROC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999009 " +
+              "NAME 'testRemoveNameFormReferencedByDSRDSR' " +
+              "FORM testRemoveNameFormReferencedByDSRNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: nameForms",
+         "nameForms: ( testremovenameformreferencedbydsrnf-oid " +
+              "NAME 'testRemoveNameFormReferencedByDSRNF' " +
+              "OC testRemoveNameFormReferencedByDSROC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    String nameFormName = "testremovenameformreferencedbydsrnf";
+    assertFalse(DirectoryServer.getSchema().hasNameForm(nameFormName));
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasNameForm(nameFormName));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule that doesn't already exist.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditcontentrulesuccessfuloc-oid " +
+              "NAME 'testAddDITContentRuleSuccessfulOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentrulesuccessfuloc-oid " +
+              "NAME 'testAddDITContentRuleSuccessful' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    String ocName = "testaddditcontentrulesuccessfuloc";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    ObjectClass oc = DirectoryServer.getSchema().getObjectClass(ocName);
+    assertNotNull(oc);
+
+    DITContentRule dcr = DirectoryServer.getSchema().getDITContentRule(oc);
+    assertNotNull(dcr);
+    assertTrue(dcr.hasName("testaddditcontentrulesuccessful"));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to replace an
+   * existing DIT content rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testReplaceDITContentRuleSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testreplaceditcontentrulesuccessfuloc-oid " +
+              "NAME 'testReplaceDITContentRuleSuccessfulOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testreplaceditcontentrulesuccessfuloc-oid " +
+              "NAME 'testReplaceDITContentRuleSuccessful' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: ditContentRules",
+         "ditContentRules: ( testreplaceditcontentrulesuccessfuloc-oid " +
+              "NAME 'testReplaceDITContentRuleSuccessful' MAY sn " +
+              "NOT description X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    String ocName = "testreplaceditcontentrulesuccessfuloc";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    ObjectClass oc = DirectoryServer.getSchema().getObjectClass(ocName);
+    assertNotNull(oc);
+
+    DITContentRule dcr = DirectoryServer.getSchema().getDITContentRule(oc);
+    assertNotNull(dcr);
+    assertTrue(dcr.hasName("testreplaceditcontentrulesuccessful"));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule to an alternate schema file.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleToAltSchemaFile()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testadddcrtoaltschemafileoc-oid " +
+              "NAME 'testAddDCRToAltSchemaFileOC' SUP top STRUCTURAL " +
+              "MUST cn X-SCHEMA-FILE '98-schema-test-dcr.ldif' " +
+              "X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testadddcrtoaltschemafileoc-oid " +
+              "NAME 'testAddDCRToAltSchemaFile' NOT description " +
+              "X-SCHEMA-FILE '98-schema-test-dcr.ldif' " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    String ocName = "testadddcrtoaltschemafileoc";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    File schemaFile = new File(SchemaConfigManager.getSchemaDirectoryPath(),
+                               "98-schema-test-dcr.ldif");
+    assertFalse(schemaFile.exists());
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    ObjectClass oc = DirectoryServer.getSchema().getObjectClass(ocName);
+    assertNotNull(oc);
+
+    DITContentRule dcr = DirectoryServer.getSchema().getDITContentRule(oc);
+    assertNotNull(dcr);
+    assertTrue(dcr.hasName("testadddcrtoaltschemafile"));
+
+    assertTrue(schemaFile.exists());
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing DIT content rule and add it back in the same operation.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveThenAddDITContentRule()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremovethenaddditcontentruleoc-oid " +
+              "NAME 'testRemoveThenAddDITContentRuleOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testremovethenaddditcontentruleoc-oid " +
+              "NAME 'testRemoveThenAddDITContentRule' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: ditContentRules",
+         "ditContentRules: ( testremovethenaddditcontentruleoc-oid " +
+              "NAME 'testRemoveThenAddDITContentRule' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testremovethenaddditcontentruleoc-oid " +
+              "NAME 'testRemoveThenAddDITContentRule' MAY sn " +
+              "NOT description X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    String ocName = "testremovethenaddditcontentruleoc";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    ObjectClass oc = DirectoryServer.getSchema().getObjectClass(ocName);
+    assertNotNull(oc);
+
+    DITContentRule dcr = DirectoryServer.getSchema().getDITContentRule(oc);
+    assertNotNull(dcr);
+    assertTrue(dcr.hasName("testremovethenaddditcontentrule"));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule whose structural objectclass is not defined in the schema.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleUndefinedOC()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: ditContentRules",
+         "ditContentRules: ( xxxundefinedxxx-oid " +
+              "NAME 'testAddDITContentRuleUndefinedOC' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule whose structural objectclass is not actually structural.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleAuxiliaryOC()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditcontentruleauxiliaryococ-oid " +
+              "NAME 'testAddDITContentRuleAuxiliaryOCOC' SUP top AUXILIARY " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentruleauxiliaryococ-oid " +
+              "NAME 'testAddDITContentRuleAuxiliaryOC' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule whose structural objectclass is already referenced by an
+   * existing DIT content rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleConflictingOC()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditcontentruleconflictingococ-oid " +
+              "NAME 'testAddDITContentRuleConflictingOCOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentruleconflictingococ-oid " +
+              "NAME 'testAddDITContentRuleConflictingOC' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentruleconflictingococ-oid " +
+              "NAME 'testAddDITContentRuleConflictingOC2' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule with an undefined auxiliary objectclass.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleUndefinedAuxOC()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditcontentruleundefinedauxococ-oid " +
+              "NAME 'testAddDITContentRuleUndefinedAuxOCOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentruleundefinedauxococ-oid " +
+              "NAME 'testAddDITContentRuleUndefinedAuxOC' " +
+              "AUX xxxundefinedxxx X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule that references an undefined required attribute type.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleUndefinedReqAT()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditcontentruleundefinedreqatoc-oid " +
+              "NAME 'testAddDITContentRuleAuxiliaryOCOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentruleundefinedreqatoc-oid " +
+              "NAME 'testAddDITContentRuleUndefinedReqAT' " +
+              "MUST xxxundefinedxxx X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule that references an undefined optional attribute type.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleUndefinedOptAT()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditcontentruleundefinedoptatoc-oid " +
+              "NAME 'testAddDITContentRuleAuxiliaryOCOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentruleundefinedoptatoc-oid " +
+              "NAME 'testAddDITContentRuleUndefinedOptAT' " +
+              "MAY xxxundefinedxxx X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new DIT
+   * content rule that references an undefined prohibited attribute type.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITContentRuleUndefinedNotAT()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditcontentruleundefinednotatoc-oid " +
+              "NAME 'testAddDITContentRuleAuxiliaryOCOC' SUP top STRUCTURAL " +
+              "MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testaddditcontentruleundefinednotatoc-oid " +
+              "NAME 'testAddDITContentRuleUndefinedNotAT' " +
+              "NOT xxxundefinedxxx X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing DIT content rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveDITContentRuleSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremoveditcontentrulesuccessfuloc-oid " +
+              "NAME 'testRemoveDITContentRuleSuccessfulOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: ditContentRules",
+         "ditContentRules: ( testremoveditcontentrulesuccessfuloc-oid " +
+              "NAME 'testRemoveDITContentRuleSuccessful' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: ditContentRules",
+         "ditContentRules: ( testremoveditcontentrulesuccessfuloc-oid " +
+              "NAME 'testRemoveDITContentRuleSuccessful' NOT description " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    String ocName = "testremoveditcontentrulesuccessfuloc";
+    assertFalse(DirectoryServer.getSchema().hasObjectClass(ocName));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    ObjectClass oc = DirectoryServer.getSchema().getObjectClass(ocName);
+    assertNotNull(oc);
+
+    DITContentRule dcr = DirectoryServer.getSchema().getDITContentRule(oc);
+    assertNull(dcr);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * DIT structure rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITStructureRuleSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditstructurerulesuccessfuloc-oid " +
+              "NAME 'testAddDITStructureRuleSuccessfulOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testaddditstructurerulesuccessfulnf-oid " +
+              "NAME 'testAddDITStructureRuleSuccessfulNF' " +
+              "OC testAddDITStructureRuleSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999001 " +
+              "NAME 'testAddDITStructureRuleSuccessful' " +
+              "FORM testAddDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999001;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to replace an
+   * existing DIT structure rule definition.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testReplaceDITStructureRuleSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testreplaceditstructurerulesuccessfuloc-oid " +
+              "NAME 'testReplaceDITStructureRuleSuccessfulOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testreplaceditstructurerulesuccessfulnf-oid " +
+              "NAME 'testReplaceDITStructureRuleSuccessfulNF' " +
+              "OC testReplaceDITStructureRuleSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999002 " +
+              "NAME 'testReplaceDITStructureRuleSuccessful' " +
+              "FORM testReplaceDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999002 " +
+              "NAME 'testReplaceDITStructureRuleSuccessful' " +
+              "DESC 'Testing the replacement of an existing DSR' " +
+              "FORM testReplaceDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999002;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * DIT structure rule to an alternate schema file.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITStructureRuleToAltSchemaFile()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testaddditstructureruletoaltschemafileoc-oid " +
+              "NAME 'testAddDITStructureRuleToAltSchemaFileOC' SUP top " +
+              "STRUCTURAL MUST cn X-SCHEMA-FILE '98-schema-test-dsr.ldif' " +
+              "X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testaddditstructureruletoaltschemafilenf-oid " +
+              "NAME 'testAddDITStructureRuleToAltSchemaFileNF' " +
+              "OC testAddDITStructureRuleToAltSchemaFileOC MUST cn " +
+              "X-SCHEMA-FILE '98-schema-test-dsr.ldif' " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999010 " +
+              "NAME 'testAddDITStructureRuleToAltSchemaFile' " +
+              "FORM testAddDITStructureRuleToAltSchemaFileNF " +
+              "X-SCHEMA-FILE '98-schema-test-dsr.ldif' " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999010;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    File schemaFile = new File(SchemaConfigManager.getSchemaDirectoryPath(),
+                               "98-schema-test-dsr.ldif");
+    assertFalse(schemaFile.exists());
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertTrue(schemaFile.exists());
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing DIT structure rule definition and add it back in the same
+   * operation.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAndAddDITStructureRuleSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremoveandaddditstructurerulesuccessfuloc-oid " +
+              "NAME 'testRemoveAndAddDITStructureRuleSuccessfulOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremoveandaddditstructurerulesuccessfulnf-oid " +
+              "NAME 'testRemoveAndAddDITStructureRuleSuccessfulNF' " +
+              "OC testRemoveAndAddDITStructureRuleSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999003 " +
+              "NAME 'testRemoveAndAddDITStructureRuleSuccessful' " +
+              "FORM testRemoveAndAddDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: ditStructureRules",
+         "ditStructureRules: ( 999003 " +
+              "NAME 'testRemoveAndAddDITStructureRuleSuccessful' " +
+              "FORM testRemoveAndAddDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999003 " +
+              "NAME 'testRemoveAndAddDITStructureRuleSuccessful' " +
+              "DESC 'Testing removing and re-adding an existing DSR' " +
+              "FORM testRemoveAndAddDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999003;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertTrue(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * DIT structure rule with an undefined name form.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITStructureRuleUndefinedNameForm()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999004 " +
+              "NAME 'testAddDITStructureRuleUndefinedNameForm' " +
+              "FORM xxxundefinedxxx " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999004;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * DIT structure rule that references an undefined superior rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddDITStructureRuleUndefinedSuperior()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testadddsrundefinedsuperioroc-oid " +
+              "NAME 'testAddDSRUndefinedSuperiorOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testadddsrundefinedsuperiornf-oid " +
+              "NAME 'testAddDSRUndefinedSuperiorNF' " +
+              "OC testAddDSRUndefinedSuperiorOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999005 " +
+              "NAME 'testAddDSRUndefinedSuperior' " +
+              "FORM testAddDSRUndefinedSuperiorNF SUP 999000 " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999005;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing DIT structure rule definition.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveDITStructureRuleSuccessful()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremoveditstructurerulesuccessfuloc-oid " +
+              "NAME 'testRemoveDITStructureRuleSuccessfulOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremoveditstructurerulesuccessfulnf-oid " +
+              "NAME 'testRemoveDITStructureRuleSuccessfulNF' " +
+              "OC testRemoveDITStructureRuleSuccessfulOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999006 " +
+              "NAME 'testRemoveDITStructureRuleSuccessful' " +
+              "FORM testRemoveDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: ditStructureRules",
+         "ditStructureRules: ( 999006 " +
+              "NAME 'testRemoveDITStructureRuleSuccessful' " +
+              "FORM testRemoveDITStructureRuleSuccessfulNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999006;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing DIT structure rule definition which is the superior rule for
+   * another DIT structure rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveSuperiorDITStructureRule()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: objectClasses",
+         "objectClasses:  ( testremovesuperiorditstructureruleoc-oid " +
+              "NAME 'testRemoveSuperiorDITStructureRuleOC' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "objectClasses:  ( testremovesuperiorditstructureruleoc2-oid " +
+              "NAME 'testRemoveSuperiorDITStructureRuleOC2' SUP top " +
+              "STRUCTURAL MUST cn X-ORIGIN 'SchemaBackendTestCase')",
+         "-",
+         "add: nameForms",
+         "nameForms: ( testremovesuperiorditstructurerulenf-oid " +
+              "NAME 'testRemoveSuperiorDITStructureRuleNF' " +
+              "OC testRemoveSuperiorDITStructureRuleOC MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "nameForms: ( testremovesuperiorditstructurerulenf2-oid " +
+              "NAME 'testRemoveSuperiorDITStructureRuleNF2' " +
+              "OC testRemoveSuperiorDITStructureRuleOC2 MUST cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: ditStructureRules",
+         "ditStructureRules: ( 999007 " +
+              "NAME 'testRemoveSuperiorDITStructureRule' " +
+              "FORM testRemoveSuperiorDITStructureRuleNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "ditStructureRules: ( 999008 " +
+              "NAME 'testRemoveSuperiorDITStructureRule2' " +
+              "FORM testRemoveSuperiorDITStructureRuleNF2 SUP 999007 " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: ditStructureRules",
+         "ditStructureRules: ( 999007 " +
+              "NAME 'testRemoveSuperiorDITStructureRule' " +
+              "FORM testRemoveSuperiorDITStructureRuleNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    int ruleID = 999007;
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+    assertTrue(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+
+
+    path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: ditStructureRules",
+         "ditStructureRules: ( 999008 " +
+              "NAME 'testRemoveSuperiorDITStructureRule2' " +
+              "FORM testRemoveSuperiorDITStructureRuleNF2 SUP 999007 " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "ditStructureRules: ( 999007 " +
+              "NAME 'testRemoveSuperiorDITStructureRule' " +
+              "FORM testRemoveSuperiorDITStructureRuleNF " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    args = new String[]
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+    assertFalse(DirectoryServer.getSchema().hasDITStructureRule(ruleID));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * matching rule use that doesn't already exist.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddMatchingRuleUseSuccessful()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testAddMRUSuccessfulMatch",
+                                    "1.3.6.1.4.1.26027.1.999.10");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.10 " +
+              "NAME 'testAddMRUSuccessful' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    assertFalse(DirectoryServer.getSchema().hasMatchingRuleUse(matchingRule));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    MatchingRuleUse mru =
+         DirectoryServer.getSchema().getMatchingRuleUse(matchingRule);
+    assertNotNull(mru);
+    assertTrue(mru.hasName("testaddmrusuccessful"));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * matching rule to an alternate schema file.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddMatchingRuleUseToAltSchemaFile()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testAddMRUToAltSchemaFileMatch",
+                                    "1.3.6.1.4.1.26027.1.999.18");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.18 " +
+              "NAME 'testAddMRUToAltSchemaFile' APPLIES cn " +
+              "X-SCHEMA-FILE '98-schema-test-mru.ldif' " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    assertFalse(DirectoryServer.getSchema().hasMatchingRuleUse(matchingRule));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    File schemaFile = new File(SchemaConfigManager.getSchemaDirectoryPath(),
+                               "98-schema-test-mru.ldif");
+    assertFalse(schemaFile.exists());
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    MatchingRuleUse mru =
+         DirectoryServer.getSchema().getMatchingRuleUse(matchingRule);
+    assertNotNull(mru);
+    assertTrue(mru.hasName("testaddmrutoaltschemafile"));
+
+    assertTrue(schemaFile.exists());
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to replace an
+   * existing matching rule use.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testReplaceMatchingRuleUseSuccessful()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testReplaceMRUSuccessfulMatch",
+                                    "1.3.6.1.4.1.26027.1.999.11");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.11 " +
+              "NAME 'testReplaceMRUSuccessful' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.11 " +
+              "NAME 'testReplaceMRUSuccessful' APPLIES ( cn $ sn ) " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    assertFalse(DirectoryServer.getSchema().hasMatchingRuleUse(matchingRule));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    MatchingRuleUse mru =
+         DirectoryServer.getSchema().getMatchingRuleUse(matchingRule);
+    assertNotNull(mru);
+    assertTrue(mru.hasName("testreplacemrusuccessful"));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove and
+   * re-add an existing matching rule use in the same operation.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveAndAddMatchingRuleUse()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testRemoveAndAddMRUMatch",
+                                    "1.3.6.1.4.1.26027.1.999.12");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.12 " +
+              "NAME 'testRemoveAndAddMRU' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.12 " +
+              "NAME 'testRemoveAndAddMRU' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "-",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.12 " +
+              "NAME 'testRemoveAndAddMRU' APPLIES ( cn $ sn ) " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    assertFalse(DirectoryServer.getSchema().hasMatchingRuleUse(matchingRule));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    MatchingRuleUse mru =
+         DirectoryServer.getSchema().getMatchingRuleUse(matchingRule);
+    assertNotNull(mru);
+    assertTrue(mru.hasName("testremoveandaddmru"));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a matching
+   * rule use that references the same matching rule as another matching rule
+   * use.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddMatchingRuleUseMRConflict()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testAddMRUMRConflictMatch",
+                                    "1.3.6.1.4.1.26027.1.999.14");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.14 " +
+              "NAME 'testAddMRUMRConflict' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.14 " +
+              "NAME 'testAddMRUMRConflict2' APPLIES sn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    assertFalse(DirectoryServer.getSchema().hasMatchingRuleUse(matchingRule));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+
+    MatchingRuleUse mru =
+         DirectoryServer.getSchema().getMatchingRuleUse(matchingRule);
+    assertNotNull(mru);
+    assertTrue(mru.hasName("testaddmrumrconflict"));
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * matching rule use that references an undefined matching rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddMatchingRuleUseMRUndefined()
+         throws Exception
+  {
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.15 " +
+              "NAME 'testAddMRUMRUndefined' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to add a new
+   * matching rule use that references an undefined attribute type.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testAddMatchingRuleUseAttributeTypeUndefined()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testAddMRUATUndefinedMatch",
+                                    "1.3.6.1.4.1.26027.1.999.16");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.16 " +
+              "NAME 'testAddMatchingRuleUseATUndefined' " +
+              "APPLIES xxxundefinedxxx " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    assertFalse(DirectoryServer.getSchema().hasMatchingRuleUse(matchingRule));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertFalse(LDAPModify.mainModify(args, false, null, null) == 0);
+  }
+
+
+
+  /**
+   * Tests the behavior of the schema backend when attempting to remove an
+   * existing matching rule use.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testRemoveMatchingRuleUseSuccessful()
+         throws Exception
+  {
+    SchemaTestMatchingRule matchingRule =
+         new SchemaTestMatchingRule("testRemoveMRUSuccessfulMatch",
+                                    "1.3.6.1.4.1.26027.1.999.13");
+    DirectoryServer.registerMatchingRule(matchingRule, false);
+
+
+    String path = TestCaseUtils.createTempFile(
+         "dn: cn=schema",
+         "changetype: modify",
+         "add: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.13 " +
+              "NAME 'testRemoveMRUSuccessful' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )",
+         "",
+         "dn: cn=schema",
+         "changetype: modify",
+         "delete: matchingRuleUse",
+         "matchingRuleUse: ( 1.3.6.1.4.1.26027.1.999.13 " +
+              "NAME 'testRemoveMRUSuccessful' APPLIES cn " +
+              "X-ORIGIN 'SchemaBackendTestCase' )");
+
+    assertFalse(DirectoryServer.getSchema().hasMatchingRuleUse(matchingRule));
+
+    String[] args =
+    {
+      "-h", "127.0.0.1",
+      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
+      "-D", "cn=Directory Manager",
+      "-w", "password",
+      "-f", path
+    };
+
+    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
+
+    MatchingRuleUse mru =
+         DirectoryServer.getSchema().getMatchingRuleUse(matchingRule);
+    assertNull(mru);
+  }
+
+
+
+  /**
+   * Tests the {@code exportLDIF} method with a valid configuration.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testExportLDIF()
+         throws Exception
+  {
+    DN configEntryDN =
+            DN.decode("ds-cfg-backend-id=schema,cn=Backends,cn=config");
+    DN[] baseDNs = { DN.decode("cn=schema") };
+
+    ConfigEntry configEntry =
+         DirectoryServer.getConfigHandler().getConfigEntry(configEntryDN);
+
+    File tempFile = File.createTempFile("schema", "testExportLDIF");
+    tempFile.deleteOnExit();
+    LDIFExportConfig exportConfig =
+         new LDIFExportConfig(tempFile.getAbsolutePath(),
+                              ExistingFileBehavior.OVERWRITE);
+
+    schemaBackend.exportLDIF(configEntry, baseDNs, exportConfig);
+
+    assertTrue(tempFile.exists());
+    assertTrue(tempFile.length() > 0);
+  }
+
+
+
+  /**
+   * Tests the {@code importLDIF} method to ensure that it throws an exception.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test(expectedExceptions = { DirectoryException.class })
+  public void testImportLDIF()
+         throws Exception
+  {
+    DN configEntryDN =
+            DN.decode("ds-cfg-backend-id=schema,cn=Backends,cn=config");
+    DN[] baseDNs = { DN.decode("cn=schema") };
+
+    ConfigEntry configEntry =
+         DirectoryServer.getConfigHandler().getConfigEntry(configEntryDN);
+
+    File tempFile = File.createTempFile("schema", "testImportLDIF");
+    tempFile.deleteOnExit();
+
+    LDIFImportConfig importConfig =
+         new LDIFImportConfig(tempFile.getAbsolutePath());
+
+    schemaBackend.importLDIF(configEntry, baseDNs, importConfig);
+  }
+
+
+
+  /**
+   * Tests the {@code getComponentEntryDN} method.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  @Test()
+  public void testGetComponentEntryDN()
+         throws Exception
+  {
+    DN configEntryDN =
+            DN.decode("ds-cfg-backend-id=schema,cn=Backends,cn=config");
+    assertEquals(schemaBackend.getComponentEntryDN(), configEntryDN);
+  }
+
+
+
+  /**
+   * Tests the {@code getClassName} method.
+   */
+  @Test()
+  public void testGetClassName()
+  {
+    assertEquals(schemaBackend.getClassName(), SchemaBackend.class.getName());
+  }
+
+
+
+  /**
+   * Tests the {@code getAlerts} method.
+   */
+  @Test()
+  public void testGetAlerts()
+  {
+    LinkedHashMap<String,String> alerts = schemaBackend.getAlerts();
+    assertNotNull(alerts);
+    assertFalse(alerts.isEmpty());
+  }
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaTestMatchingRule.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaTestMatchingRule.java
new file mode 100644
index 0000000..14f5b57
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/SchemaTestMatchingRule.java
@@ -0,0 +1,190 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.backends;
+
+
+
+import org.opends.server.api.EqualityMatchingRule;
+import org.opends.server.config.ConfigEntry;
+import org.opends.server.config.ConfigException;
+import org.opends.server.schema.CaseIgnoreEqualityMatchingRule;
+import org.opends.server.types.ByteString;
+import org.opends.server.types.DirectoryException;
+import org.opends.server.types.InitializationException;
+
+
+
+/**
+ * This class implements an equality matching rule that is intended for testing
+ * purposes within the server (e.g., in conjunction with matching rule uses).
+ * For all practical purposes, it behaves like the standard caseIgnoreMatch
+ * matching rule.
+ */
+public class SchemaTestMatchingRule
+       extends EqualityMatchingRule
+{
+  // The matching rule that will do all the real work behind the scenes.
+  private CaseIgnoreEqualityMatchingRule caseIgnoreMatchingRule;
+
+  // The name for this matching rule.
+  private String name;
+
+  // The OID for this matching rule.
+  private String oid;
+
+
+
+  /**
+   * Creates a new instance of this matching rule with the provided information.
+   *
+   * @param  name  The name to use for this matching rule.
+   * @param  oid   The OID to use for this matching rule.
+   *
+   * @throws  Exception  If an unexpected problem occurs.
+   */
+  public SchemaTestMatchingRule(String name, String oid)
+         throws Exception
+  {
+    super();
+
+    this.name = name;
+    this.oid  = oid;
+
+    caseIgnoreMatchingRule = new CaseIgnoreEqualityMatchingRule();
+    caseIgnoreMatchingRule.initializeMatchingRule(null);
+  }
+
+
+
+  /**
+   * Initializes this matching rule based on the information in the provided
+   * configuration entry.
+   *
+   * @param  configEntry  The configuration entry that contains the information
+   *                      to use to initialize this matching rule.
+   *
+   * @throws  ConfigException  If an unrecoverable problem arises in the
+   *                           process of performing the initialization.
+   *
+   * @throws  InitializationException  If a problem that is not
+   *                                   configuration-related occurs during
+   *                                   initialization.
+   */
+  public void initializeMatchingRule(ConfigEntry configEntry)
+         throws ConfigException, InitializationException
+  {
+    // No initialization is required.
+  }
+
+
+
+  /**
+   * Retrieves the common name for this matching rule.
+   *
+   * @return  The common name for this matching rule, or <CODE>null</CODE> if
+   * it does not have a name.
+   */
+  public String getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this matching rule.
+   *
+   * @return  The OID for this matching rule.
+   */
+  public String getOID()
+  {
+    return oid;
+  }
+
+
+
+  /**
+   * Retrieves the description for this matching rule.
+   *
+   * @return  The description for this matching rule, or <CODE>null</CODE> if
+   *          there is none.
+   */
+  public String getDescription()
+  {
+    return null;
+  }
+
+
+
+  /**
+   * Retrieves the OID of the syntax with which this matching rule is
+   * associated.
+   *
+   * @return  The OID of the syntax with which this matching rule is associated.
+   */
+  public String getSyntaxOID()
+  {
+    return caseIgnoreMatchingRule.getSyntaxOID();
+  }
+
+
+
+  /**
+   * Retrieves the normalized form of the provided value, which is best suited
+   * for efficiently performing matching operations on that value.
+   *
+   * @param  value  The value to be normalized.
+   *
+   * @return  The normalized version of the provided value.
+   *
+   * @throws  DirectoryException  If the provided value is invalid according to
+   *                              the associated attribute syntax.
+   */
+  public ByteString normalizeValue(ByteString value)
+         throws DirectoryException
+  {
+    return caseIgnoreMatchingRule.normalizeValue(value);
+  }
+
+
+
+  /**
+   * Indicates whether the two provided normalized values are equal to each
+   * other.
+   *
+   * @param  value1  The normalized form of the first value to compare.
+   * @param  value2  The normalized form of the second value to compare.
+   *
+   * @return  <CODE>true</CODE> if the provided values are equal, or
+   *          <CODE>false</CODE> if not.
+   */
+  public boolean areEqual(ByteString value1, ByteString value2)
+  {
+    return caseIgnoreMatchingRule.areEqual(value1, value2);
+  }
+}
+
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestAttributeType.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestAttributeType.java
index 949bfde..d78b714 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestAttributeType.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestAttributeType.java
@@ -22,13 +22,15 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
 
 
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -139,12 +141,157 @@
     protected AttributeType buildInstance(String primaryName,
         Collection<String> names, String oid, String description,
         boolean isObsolete, Map<String, List<String>> extraProperties) {
-      return new AttributeType(primaryName, names, oid, description,
-          superiorType, syntax, approximateMatchingRule,
-          equalityMatchingRule, orderingMatchingRule,
-          substringMatchingRule, attributeUsage, isCollective,
-          isNoUserModification, isObsolete, isSingleValue,
-          extraProperties);
+
+      StringBuilder definition = new StringBuilder();
+      definition.append("( ");
+      definition.append(oid);
+
+      LinkedHashSet<String> nameSet = new LinkedHashSet<String>();
+      if (primaryName != null)
+      {
+        nameSet.add(primaryName);
+      }
+
+      if (names != null)
+      {
+        for (String name : names)
+        {
+          nameSet.add(name);
+        }
+      }
+
+      if (! nameSet.isEmpty())
+      {
+        if (nameSet.size() == 1)
+        {
+          definition.append(" NAME '");
+          definition.append(nameSet.iterator().next());
+          definition.append("'");
+        }
+        else
+        {
+          Iterator<String> iterator = nameSet.iterator();
+
+          definition.append(" NAME ( '");
+          definition.append(iterator.next());
+
+          while (iterator.hasNext())
+          {
+            definition.append("' '");
+            definition.append(iterator.next());
+          }
+
+          definition.append("' )");
+        }
+      }
+
+      if (description != null)
+      {
+        definition.append(" DESC '");
+        definition.append(description);
+        definition.append("'");
+      }
+
+      if (isObsolete)
+      {
+        definition.append(" OBSOLETE");
+      }
+
+      if (superiorType != null)
+      {
+        definition.append(" SUP ");
+        definition.append(superiorType.getNameOrOID());
+      }
+
+      if (equalityMatchingRule != null)
+      {
+        definition.append(" EQUALITY ");
+        definition.append(equalityMatchingRule.getNameOrOID());
+      }
+
+      if (orderingMatchingRule != null)
+      {
+        definition.append(" ORDERING ");
+        definition.append(orderingMatchingRule.getNameOrOID());
+      }
+
+      if (substringMatchingRule != null)
+      {
+        definition.append(" SUBSTR ");
+        definition.append(substringMatchingRule.getNameOrOID());
+      }
+
+      if (syntax != null)
+      {
+        definition.append(" SYNTAX ");
+        definition.append(syntax.getOID());
+      }
+
+      if (isSingleValue)
+      {
+        definition.append(" SINGLE-VALUE");
+      }
+
+      if (isCollective)
+      {
+        definition.append(" COLLECTIVE");
+      }
+
+      if (isNoUserModification)
+      {
+        definition.append(" NO-USER-MODIFICATIOn");
+      }
+
+      if (attributeUsage != null)
+      {
+        definition.append(" USAGE ");
+        definition.append(attributeUsage.toString());
+      }
+
+      if (extraProperties != null)
+      {
+        for (String property : extraProperties.keySet())
+        {
+          List<String> values = extraProperties.get(property);
+          if ((values == null) || values.isEmpty())
+          {
+            continue;
+          }
+          else if (values.size() == 1)
+          {
+            definition.append(" ");
+            definition.append(property);
+            definition.append(" '");
+            definition.append(values.get(0));
+            definition.append("'");
+          }
+          else
+          {
+            definition.append(" ");
+            definition.append(property);
+            definition.append(" (");
+            for (String value : values)
+            {
+              definition.append(" '");
+              definition.append(value);
+              definition.append("'");
+            }
+
+            definition.append(" )");
+          }
+        }
+      }
+
+      definition.append(" )");
+
+
+      return new AttributeType(definition.toString(), primaryName, names, oid,
+                               description, superiorType, syntax,
+                               approximateMatchingRule, equalityMatchingRule,
+                               orderingMatchingRule, substringMatchingRule,
+                               attributeUsage, isCollective,
+                               isNoUserModification, isObsolete, isSingleValue,
+                               extraProperties);
     }
 
 
@@ -283,7 +430,7 @@
    */
   @Test(expectedExceptions = NullPointerException.class)
   public void testSimpleConstructorNPE() throws Exception {
-    new AttributeType(null, null, null, null, null, null, null,
+    new AttributeType(null, null, null, null, null, null, null, null,
         false, false, false, false);
   }
 
@@ -298,7 +445,7 @@
    */
   @Test(expectedExceptions = NullPointerException.class)
   public void testComplexConstructorNPE() throws Exception {
-    new AttributeType(null, null, null, null, null, null, null, null,
+    new AttributeType(null, null, null, null, null, null, null, null, null,
         null, null, null, false, false, false, false, null);
   }
 
@@ -313,7 +460,7 @@
    */
   @Test
   public void testComplexConstructorDefault() throws Exception {
-    AttributeType type = new AttributeType(null, null, "1.2.3", null,
+    AttributeType type = new AttributeType("", null, null, "1.2.3", null,
         null, null, null, null, null, null, null, false, false,
         false, false, null);
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestObjectClass.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestObjectClass.java
index 41fc1da..2a5390f 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestObjectClass.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/types/TestObjectClass.java
@@ -22,7 +22,7 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2006 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
 package org.opends.server.types;
 
@@ -31,6 +31,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -112,9 +113,163 @@
     protected ObjectClass buildInstance(String primaryName,
         Collection<String> names, String oid, String description,
         boolean isObsolete, Map<String, List<String>> extraProperties) {
-      return new ObjectClass(primaryName, names, oid, description,
-          superior, requiredAttributeTypes, optionalAttributeTypes,
-          objectClassType, isObsolete, extraProperties);
+
+      StringBuilder definition = new StringBuilder();
+      definition.append("( ");
+      definition.append(oid);
+
+      LinkedHashSet<String> nameSet = new LinkedHashSet<String>();
+      if (primaryName != null)
+      {
+        nameSet.add(primaryName);
+      }
+
+      if (names != null)
+      {
+        for (String name : names)
+        {
+          nameSet.add(name);
+        }
+      }
+
+      if (! nameSet.isEmpty())
+      {
+        if (nameSet.size() == 1)
+        {
+          definition.append(" NAME '");
+          definition.append(nameSet.iterator().next());
+          definition.append("'");
+        }
+        else
+        {
+          Iterator<String> iterator = nameSet.iterator();
+
+          definition.append(" NAME ( '");
+          definition.append(iterator.next());
+
+          while (iterator.hasNext())
+          {
+            definition.append("' '");
+            definition.append(iterator.next());
+          }
+
+          definition.append("' )");
+        }
+      }
+
+      if (description != null)
+      {
+        definition.append(" DESC '");
+        definition.append(description);
+        definition.append("'");
+      }
+
+      if (isObsolete)
+      {
+        definition.append(" OBSOLETE");
+      }
+
+      if (superior != null)
+      {
+        definition.append(" SUP ");
+        definition.append(superior.getNameOrOID());
+      }
+
+      if (objectClassType != null)
+      {
+        definition.append(" ");
+        definition.append(objectClassType.toString());
+      }
+
+      if ((requiredAttributeTypes != null) &&
+          (! requiredAttributeTypes.isEmpty()))
+      {
+        if (requiredAttributeTypes.size() == 1)
+        {
+          definition.append(" MUST ");
+          definition.append(
+               requiredAttributeTypes.iterator().next().getNameOrOID());
+        }
+        else
+        {
+          Iterator<AttributeType> iterator = requiredAttributeTypes.iterator();
+
+          definition.append(" MUST ( ");
+          definition.append(iterator.next().getNameOrOID());
+          while (iterator.hasNext())
+          {
+            definition.append(" $ ");
+            definition.append(iterator.next().getNameOrOID());
+          }
+          definition.append(" )");
+        }
+      }
+
+      if ((optionalAttributeTypes != null) &&
+          (! optionalAttributeTypes.isEmpty()))
+      {
+        if (optionalAttributeTypes.size() == 1)
+        {
+          definition.append(" MUST ");
+          definition.append(
+               optionalAttributeTypes.iterator().next().getNameOrOID());
+        }
+        else
+        {
+          Iterator<AttributeType> iterator = optionalAttributeTypes.iterator();
+
+          definition.append(" MUST ( ");
+          definition.append(iterator.next().getNameOrOID());
+          while (iterator.hasNext())
+          {
+            definition.append(" $ ");
+            definition.append(iterator.next().getNameOrOID());
+          }
+          definition.append(" )");
+        }
+      }
+
+      if (extraProperties != null)
+      {
+        for (String property : extraProperties.keySet())
+        {
+          List<String> values = extraProperties.get(property);
+          if ((values == null) || values.isEmpty())
+          {
+            continue;
+          }
+          else if (values.size() == 1)
+          {
+            definition.append(" ");
+            definition.append(property);
+            definition.append(" '");
+            definition.append(values.get(0));
+            definition.append("'");
+          }
+          else
+          {
+            definition.append(" ");
+            definition.append(property);
+            definition.append(" (");
+            for (String value : values)
+            {
+              definition.append(" '");
+              definition.append(value);
+              definition.append("'");
+            }
+
+            definition.append(" )");
+          }
+        }
+      }
+
+      definition.append(" )");
+
+
+      return new ObjectClass(definition.toString(), primaryName, names, oid,
+                             description, superior, requiredAttributeTypes,
+                             optionalAttributeTypes, objectClassType,
+                             isObsolete, extraProperties);
     }
 
 
@@ -219,7 +374,7 @@
     Set<AttributeType> emptySet = Collections.emptySet();
     Map<String, List<String>> emptyMap = Collections.emptyMap();
 
-    new ObjectClass("test", Collections.singleton("test"), null,
+    new ObjectClass(null, "test", Collections.singleton("test"), null,
         "description", DirectoryServer.getTopObjectClass(), emptySet,
         emptySet, ObjectClassType.STRUCTURAL, false, emptyMap);
   }
@@ -235,7 +390,8 @@
    */
   @Test
   public void testConstructorDefault() throws Exception {
-    ObjectClass type = new ObjectClass(null, null, "1.2.3", null,
+    String definition = "( 1.2.3 )";
+    ObjectClass type = new ObjectClass(definition, null, null, "1.2.3", null,
         null, null, null, null, false, null);
 
     Assert.assertNull(type.getPrimaryName());

--
Gitblit v1.10.0