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/backends/SchemaBackend.java | 3003 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 2,731 insertions(+), 272 deletions(-)

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;
+  }
 }
 

--
Gitblit v1.10.0