mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Nicolas Capponi
10.59.2016 3e85d5e13c0bb39af690966f4a28b8ff36f0c556
OPENDJ-3089 OPENDJ-1237 Move writing of modified schema files from SchemaBackend to SchemaWriter class

- SchemaBackend does not manage any more writing of schema files
- In SchemaHandler, the update of schema and schema file is merged into a
single method
- SchemaWriter manages writing of modified schema files
4 files modified
1325 ■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java 668 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java 34 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java 600 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/util/StaticUtils.java 23 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
@@ -31,17 +31,12 @@
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -69,6 +64,7 @@
import org.forgerock.opendj.ldap.schema.MatchingRuleUse;
import org.forgerock.opendj.ldap.schema.NameForm;
import org.forgerock.opendj.ldap.schema.ObjectClass;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaBuilder;
import org.forgerock.opendj.ldap.schema.SchemaElement;
import org.forgerock.opendj.ldap.schema.Syntax;
@@ -94,7 +90,6 @@
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.ExistingFileBehavior;
import org.opends.server.types.IndexType;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
@@ -388,7 +383,7 @@
    }
    /* Add the schema definition attributes. */
    org.forgerock.opendj.ldap.schema.Schema schema = serverContext.getSchemaNG();
    Schema schema = serverContext.getSchemaNG();
    buildSchemaAttribute(schema.getAttributeTypes(), userAttrs,
        operationalAttrs, attributeTypesType, includeSchemaFile,
        AttributeTypeSyntax.isStripSyntaxMinimumUpperBound(),
@@ -553,10 +548,8 @@
    SchemaBuilder schemaBuilder = new SchemaBuilder(schemaHandler.getSchema());
    applyModifications(schemaBuilder, mods, modifiedSchemaFiles, modifyOperation.isSynchronizationOperation());
    org.forgerock.opendj.ldap.schema.Schema newSchema = schemaBuilder.toSchema();
    schemaHandler.updateSchema(newSchema);
    updateSchemaFiles(newSchema, modifiedSchemaFiles);
    Schema newSchema = schemaBuilder.toSchema();
    schemaHandler.updateSchemaAndSchemaFiles(newSchema, modifiedSchemaFiles, this);
    DN authzDN = modifyOperation.getAuthorizationDN();
    if (authzDN == null)
@@ -802,59 +795,6 @@
  }
  /**
   * Re-write all schema files using the provided new Schema and list of
   * modified files.
   *
   * @param newSchema            The new schema that should be used.
   *
   * @param modifiedSchemaFiles  The list of files that should be modified.
   *
   * @throws DirectoryException  When the new file cannot be written.
   */
  private void updateSchemaFiles(org.forgerock.opendj.ldap.schema.Schema newSchema, TreeSet<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    // 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<>();
    try
    {
      for (String schemaFile : modifiedSchemaFiles)
      {
        File tempSchemaFile = writeTempSchemaFile(newSchema, schemaFile);
        tempSchemaFiles.put(schemaFile, tempSchemaFile);
      }
      installSchemaFiles(tempSchemaFiles);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      throw de;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage message =
          ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_SCHEMA.get(getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
    }
    finally
    {
      cleanUpTempSchemaFiles(tempSchemaFiles);
    }
    // Create a single file with all of the concatenated schema information
    // that we can use on startup to detect whether the schema files have been
    // edited with the server offline.
    SchemaWriter.writeConcatenatedSchema();
  }
  /**
   * 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.
@@ -874,7 +814,7 @@
  private void addAttributeType(String definition, SchemaBuilder schemaBuilder, Set<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaUtils.parseAttributeTypeOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasAttributeType(oid))
@@ -965,7 +905,7 @@
  private void removeAttributeType(String definition, SchemaBuilder newSchemaBuilder, List<Modification> modifications,
      int currentPosition, Set<String> modifiedSchemaFiles) throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    Schema currentSchema = newSchemaBuilder.toSchema();
    String atOID = SchemaUtils.parseAttributeTypeOID(definition);
    if (!currentSchema.hasAttributeType(atOID))
@@ -1032,7 +972,7 @@
  private void addObjectClass(String definition, SchemaBuilder schemaBuilder, Set<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaUtils.parseObjectClassOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasObjectClass(oid))
@@ -1079,7 +1019,7 @@
                                 Set<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    Schema currentSchema = newSchemaBuilder.toSchema();
    String ocOID = SchemaUtils.parseObjectClassOID(definition);
    if (!currentSchema.hasObjectClass(ocOID))
@@ -1148,7 +1088,7 @@
  private void addNameForm(String definition, SchemaBuilder schemaBuilder, Set<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaUtils.parseNameFormOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasNameForm(oid))
@@ -1194,7 +1134,7 @@
                              Set<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    Schema currentSchema = newSchemaBuilder.toSchema();
    String nfOID = SchemaUtils.parseNameFormOID(definition);
    if (!currentSchema.hasNameForm(nfOID))
@@ -1262,7 +1202,7 @@
  private void addDITContentRule(String definition, SchemaBuilder schemaBuilder,
      Set<String> modifiedSchemaFiles) throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaUtils.parseDITContentRuleOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasDITContentRule(oid))
@@ -1302,7 +1242,7 @@
  private void removeDITContentRule(String definition,
      SchemaBuilder newSchemaBuilder, Set<String> modifiedSchemaFiles) throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    Schema currentSchema = newSchemaBuilder.toSchema();
    String ruleOid = SchemaUtils.parseDITContentRuleOID(definition);
    if (! currentSchema.hasDITContentRule(ruleOid))
@@ -1340,7 +1280,7 @@
  private void addDITStructureRule(String definition, SchemaBuilder schemaBuilder, Set<String> modifiedSchemaFiles)
      throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    Schema currentSchema = schemaHandler.getSchema();
    int ruleId = SchemaUtils.parseRuleID(definition);
    final String finalDefinition;
    if (!currentSchema.hasDITStructureRule(ruleId))
@@ -1388,7 +1328,7 @@
                                      Set<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    Schema currentSchema = newSchemaBuilder.toSchema();
    int ruleID = SchemaUtils.parseRuleID(definition);
    if (!currentSchema.hasDITStructureRule(ruleID))
@@ -1448,7 +1388,7 @@
  private void addMatchingRuleUse(String definition, SchemaBuilder schemaBuilder, Set<String> modifiedSchemaFiles)
      throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaUtils.parseMatchingRuleUseOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasMatchingRuleUse(oid))
@@ -1490,7 +1430,7 @@
                                     Set<String> modifiedSchemaFiles)
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    Schema currentSchema = newSchemaBuilder.toSchema();
    String mruOid = SchemaUtils.parseMatchingRuleUseOID(definition);
    if (!currentSchema.hasMatchingRuleUse(mruOid))
@@ -1529,7 +1469,7 @@
    // TODO: not sure of the correct implementation here. There was previously a check that would
    // reject a change if a syntax with oid already exists, but I don't understand why.
    // I kept an implementation that behave like other schema elements.
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaUtils.parseSyntaxOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasSyntax(oid))
@@ -1553,7 +1493,7 @@
     * part of the ldapsyntaxes attribute. A virtual value is not searched and
     * hence never deleted.
     */
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    Schema currentSchema = newSchemaBuilder.toSchema();
    String oid = SchemaUtils.parseSyntaxOID(definition);
    if (!currentSchema.hasSyntax(oid))
@@ -1566,567 +1506,6 @@
    addElementIfNotNull(modifiedSchemaFiles, getElementSchemaFile(currentSchema.getSyntax(oid)));
  }
  /**
   * Creates an empty entry that may be used as the basis for a new schema file.
   *
   * @return  An empty entry that may be used as the basis for a new schema
   *          file.
   */
  private Entry createEmptySchemaEntry()
  {
    Map<ObjectClass,String> objectClasses = new LinkedHashMap<>();
    objectClasses.put(CoreSchema.getTopObjectClass(), OC_TOP);
    objectClasses.put(DirectoryServer.getSchema().getObjectClass(OC_LDAP_SUBENTRY_LC), OC_LDAP_SUBENTRY);
    objectClasses.put(DirectoryServer.getSchema().getObjectClass(OC_SUBSCHEMA), OC_SUBSCHEMA);
    Map<AttributeType,List<Attribute>> userAttributes = new LinkedHashMap<>();
    Map<AttributeType,List<Attribute>> operationalAttributes = new LinkedHashMap<>();
    DN  dn  = DirectoryServer.getSchemaDN();
    for (AVA ava : dn.rdn())
    {
      AttributeType type = ava.getAttributeType();
      Map<AttributeType, List<Attribute>> attrs = type.isOperational() ? operationalAttributes : userAttributes;
      attrs.put(type, newLinkedList(Attributes.create(type, ava.getAttributeValue())));
    }
    return new Entry(dn, objectClasses,  userAttributes, operationalAttributes);
  }
  /**
   * 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(org.forgerock.opendj.ldap.schema.Schema schema, String schemaFile)
          throws DirectoryException, IOException, LDIFException
  {
    Entry schemaEntry = createEmptySchemaEntry();
     /*
     * Add all of the ldap syntax descriptions to the schema entry. We do
     * this only for the real part of the ldapsyntaxes attribute. The real part
     * is read and write to/from the schema files.
     */
    Set<ByteString> values = getValuesForSchemaFile(getCustomSyntaxes(schema), schemaFile);
    addAttribute(schemaEntry, ldapSyntaxesType, values);
    // 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.
    values = getAttributeTypeValuesForSchemaFile(schema, schemaFile);
    addAttribute(schemaEntry, attributeTypesType, values);
    // 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.
    values = getObjectClassValuesForSchemaFile(schema, schemaFile);
    addAttribute(schemaEntry, objectClassesType, values);
    // 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 = getValuesForSchemaFile(schema.getNameForms(), schemaFile);
    addAttribute(schemaEntry, nameFormsType, values);
    // 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 = getValuesForSchemaFile(schema.getDITContentRules(), schemaFile);
    addAttribute(schemaEntry, ditContentRulesType, values);
    // 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.
    values = getDITStructureRuleValuesForSchemaFile(schema, schemaFile);
    addAttribute(schemaEntry, ditStructureRulesType, values);
    // 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 = getValuesForSchemaFile(schema.getMatchingRuleUses(), schemaFile);
    addAttribute(schemaEntry, matchingRuleUsesType, values);
    if (FILE_USER_SCHEMA_ELEMENTS.equals(schemaFile))
    {
      for (Attribute attribute : schemaHandler.getExtraAttributes())
      {
        AttributeType attributeType = attribute.getAttributeDescription().getAttributeType();
        schemaEntry.putAttribute(attributeType, newArrayList(attribute));
      }
    }
    // 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);
    try (LDIFWriter ldifWriter = new LDIFWriter(exportConfig))
    {
      ldifWriter.writeEntry(schemaEntry);
    }
    return tempFile;
  }
  /**
   * Returns custom syntaxes defined by OpenDJ configuration or by users.
   * <p>
   * These are non-standard syntaxes.
   *
   * @param schema
   *          the schema where to extract custom syntaxes from
   * @return custom, non-standard syntaxes
   */
  private Collection<Syntax> getCustomSyntaxes(org.forgerock.opendj.ldap.schema.Schema schema)
  {
    List<Syntax> results = new ArrayList<>();
    for (Syntax syntax : schema.getSyntaxes())
    {
      for (String propertyName : syntax.getExtraProperties().keySet())
      {
        if ("x-subst".equalsIgnoreCase(propertyName)
            || "x-pattern".equalsIgnoreCase(propertyName)
            || "x-enum".equalsIgnoreCase(propertyName)
            || "x-schema-file".equalsIgnoreCase(propertyName))
        {
          results.add(syntax);
          break;
        }
      }
    }
    return results;
  }
  private Set<ByteString> getValuesForSchemaFile(Collection<? extends SchemaElement> schemaElements, String schemaFile)
  {
    Set<ByteString> values = new LinkedHashSet<>();
    for (SchemaElement schemaElement : schemaElements)
    {
      if (schemaFile.equals(getElementSchemaFile(schemaElement)))
      {
        values.add(ByteString.valueOfUtf8(schemaElement.toString()));
      }
    }
    return values;
  }
  private Set<ByteString> getAttributeTypeValuesForSchemaFile(org.forgerock.opendj.ldap.schema.Schema schema,
      String schemaFile) throws DirectoryException
  {
    Set<AttributeType> addedTypes = new HashSet<>();
    Set<ByteString> values = new LinkedHashSet<>();
    for (AttributeType at : schema.getAttributeTypes())
    {
      if (schemaFile.equals(getElementSchemaFile(at)))
      {
        addAttrTypeToSchemaFile(schemaFile, at, values, addedTypes, 0);
      }
    }
    return values;
  }
  private Set<ByteString> getObjectClassValuesForSchemaFile(org.forgerock.opendj.ldap.schema.Schema schema,
      String schemaFile) throws DirectoryException
  {
    Set<ObjectClass> addedClasses = new HashSet<>();
    Set<ByteString> values = new LinkedHashSet<>();
    for (ObjectClass oc : schema.getObjectClasses())
    {
      if (schemaFile.equals(getElementSchemaFile(oc)))
      {
        addObjectClassToSchemaFile(schemaFile, oc, values, addedClasses, 0);
      }
    }
    return values;
  }
  private Set<ByteString> getDITStructureRuleValuesForSchemaFile(org.forgerock.opendj.ldap.schema.Schema schema,
      String schemaFile) throws DirectoryException
  {
    Set<DITStructureRule> addedDSRs = new HashSet<>();
    Set<ByteString> values = new LinkedHashSet<>();
    for (DITStructureRule dsr : schema.getDITStuctureRules())
    {
      if (schemaFile.equals(getElementSchemaFile(dsr)))
      {
        addDITStructureRuleToSchemaFile(schemaFile, dsr, values, addedDSRs, 0);
      }
    }
    return values;
  }
  private void addAttribute(Entry schemaEntry, AttributeType attrType, Set<ByteString> values)
  {
    if (!values.isEmpty())
    {
      AttributeBuilder builder = new AttributeBuilder(attrType);
      builder.addAll(values);
      schemaEntry.putAttribute(attrType, newArrayList(builder.toAttribute()));
    }
  }
  /**
   * 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(String schemaFile,
                                       AttributeType attributeType,
                                       Set<ByteString> values,
                                       Set<AttributeType> addedTypes,
                                       int depth)
          throws DirectoryException
  {
    if (depth > 20)
    {
      LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_AT.get(
          attributeType.getNameOrOID());
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }
    if (addedTypes.contains(attributeType))
    {
      return;
    }
    AttributeType superiorType = attributeType.getSuperiorType();
    if (superiorType != null &&
        schemaFile.equals(getElementSchemaFile(attributeType)) &&
        !addedTypes.contains(superiorType))
    {
      addAttrTypeToSchemaFile(schemaFile, superiorType, values, addedTypes, depth+1);
    }
    values.add(ByteString.valueOfUtf8(attributeType.toString()));
    addedTypes.add(attributeType);
  }
  /**
   * Adds the definition for the specified objectclass to the provided set of
   * attribute values, recursively adding superior classes as appropriate.
   *
   * @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(String schemaFile,
                                          ObjectClass objectClass,
                                          Set<ByteString> values,
                                          Set<ObjectClass> addedClasses,
                                          int depth)
          throws DirectoryException
  {
    if (depth > 20)
    {
      LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_OC.get(
          objectClass.getNameOrOID());
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }
    if (addedClasses.contains(objectClass))
    {
      return;
    }
    for(ObjectClass superiorClass : objectClass.getSuperiorClasses())
    {
      if (schemaFile.equals(getElementSchemaFile(superiorClass)) &&
          !addedClasses.contains(superiorClass))
      {
        addObjectClassToSchemaFile(schemaFile, superiorClass, values,
                                   addedClasses, depth+1);
      }
    }
    values.add(ByteString.valueOfUtf8(objectClass.toString()));
    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(String schemaFile,
                    DITStructureRule ditStructureRule,
                    Set<ByteString> values,
                    Set<DITStructureRule> addedDSRs, int depth)
          throws DirectoryException
  {
    if (depth > 20)
    {
      LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_DSR.get(
          ditStructureRule.getNameOrRuleID());
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }
    if (addedDSRs.contains(ditStructureRule))
    {
      return;
    }
    for (DITStructureRule dsr : ditStructureRule.getSuperiorRules())
    {
      if (schemaFile.equals(getElementSchemaFile(dsr)) && !addedDSRs.contains(dsr))
      {
        addDITStructureRuleToSchemaFile(schemaFile, dsr, values,
                                        addedDSRs, depth+1);
      }
    }
    values.add(ByteString.valueOfUtf8(ditStructureRule.toString()));
    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
  {
    // 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<>();
    ArrayList<File> tempFileList      = new ArrayList<>();
    ArrayList<File> origFileList      = new ArrayList<>();
    File schemaInstanceDir =
      new File(SchemaConfigManager.getSchemaDirectoryPath());
    for (String name : tempSchemaFiles.keySet())
    {
      installedFileList.add(new File(schemaInstanceDir, name));
      tempFileList.add(tempSchemaFiles.get(name));
      origFileList.add(new File(schemaInstanceDir, 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)
    {
      logger.traceException(e);
      boolean allCleaned = true;
      for (File f : origFileList)
      {
        try
        {
          if (f.exists() && !f.delete())
          {
            allCleaned = false;
          }
        }
        catch (Exception e2)
        {
          logger.traceException(e2);
          allCleaned = false;
        }
      }
      LocalizableMessage message;
      if (allCleaned)
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_CLEANED.get(getExceptionMessage(e));
      }
      else
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_NOT_CLEANED.get(getExceptionMessage(e));
        DirectoryServer.sendAlertNotification(this,
                             ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES,
                             message);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, 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)
    {
      logger.traceException(e);
      deleteFiles(installedFileList);
      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() && !origFile.renameTo(installedFile))
          {
            allRestored = false;
          }
        }
        catch (Exception e2)
        {
          logger.traceException(e2);
          allRestored = false;
        }
      }
      LocalizableMessage message;
      if (allRestored)
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_RESTORED.get(getExceptionMessage(e));
      }
      else
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED.get(getExceptionMessage(e));
        DirectoryServer.sendAlertNotification(this,
                             ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES,
                             message);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
    }
    deleteFiles(origFileList);
    deleteFiles(tempFileList);
  }
  private void deleteFiles(Iterable<File> files)
  {
    if (files != null)
    {
      for (File f : files)
      {
        try
        {
          if (f.exists())
          {
            f.delete();
          }
        }
        catch (Exception e)
        {
          logger.traceException(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
  {
    try (FileInputStream inputStream = new FileInputStream(from);
        FileOutputStream outputStream = new FileOutputStream(to, false))
    {
      byte[] buffer = new byte[4096];
      int bytesRead = inputStream.read(buffer);
      while (bytesRead > 0)
      {
        outputStream.write(buffer, 0, bytesRead);
        bytesRead = inputStream.read(buffer);
      }
    }
  }
  /**
   * 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)
  {
    deleteFiles(tempSchemaFiles.values());
  }
  @Override
  public void renameEntry(DN currentDN, Entry entry,
                                   ModifyDNOperation modifyDNOperation)
@@ -2328,7 +1707,7 @@
   */
  private void importEntry(Entry newSchemaEntry) throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema schema = schemaHandler.getSchema();
    Schema schema = schemaHandler.getSchema();
    SchemaBuilder newSchemaBuilder = new SchemaBuilder(schema);
    TreeSet<String> modifiedSchemaFiles = new TreeSet<>();
@@ -2437,9 +1816,8 @@
    // Finally, if there were some modifications, save the new schema
    if (!modifiedSchemaFiles.isEmpty())
    {
      org.forgerock.opendj.ldap.schema.Schema newSchema = newSchemaBuilder.toSchema();
      schemaHandler.updateSchema(newSchema);
      updateSchemaFiles(newSchema, modifiedSchemaFiles);
      Schema newSchema = newSchemaBuilder.toSchema();
      schemaHandler.updateSchemaAndSchemaFiles(newSchema, modifiedSchemaFiles, this);
    }
  }
@@ -2460,7 +1838,7 @@
    }
  }
  private boolean hasAttributeTypeDefinitionChanged(org.forgerock.opendj.ldap.schema.Schema schema, String oid,
  private boolean hasAttributeTypeDefinitionChanged(Schema schema, String oid,
      String definition)
  {
    if (schema.hasAttributeType(oid))
@@ -2472,7 +1850,7 @@
    return true;
  }
  private boolean hasObjectClassDefinitionChanged(org.forgerock.opendj.ldap.schema.Schema schema, String oid,
  private boolean hasObjectClassDefinitionChanged(Schema schema, String oid,
      String definition)
  {
    if (schema.hasObjectClass(oid))
opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
@@ -15,7 +15,6 @@
 */
package org.opends.server.core;
import static com.forgerock.opendj.ldap.CoreMessages.ERR_ATTR_SYNTAX_TRUNCATED_VALUE1;
import static org.opends.messages.ConfigMessages.WARN_CONFIG_SCHEMA_CANNOT_OPEN_FILE;
import static org.opends.server.util.ServerConstants.SCHEMA_PROPERTY_FILENAME;
import static org.opends.messages.SchemaMessages.ERR_SCHEMA_HAS_WARNINGS;
@@ -34,9 +33,11 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.opends.server.api.AlertGenerator;
import org.opends.server.replication.plugin.HistoricalCsnOrderingMatchingRuleImpl;
import org.opends.server.schema.AciSyntax;
import org.opends.server.schema.SubtreeSpecificationSyntax;
@@ -44,7 +45,6 @@
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.ClassPropertyDefinition;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.schema.ConflictingSchemaElementException;
@@ -71,6 +71,7 @@
import org.opends.server.types.Attribute;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import org.opends.server.types.SchemaWriter;
import org.opends.server.util.ActivateOnceSDKSchemaIsUsed;
import org.opends.server.util.StaticUtils;
@@ -216,7 +217,7 @@
  }
  /**
   * Update this handler with the provided schema.
   * Replaces the schema with the provided schema.
   *
   * @param schema
   *          the new schema to use
@@ -237,6 +238,33 @@
  }
  /**
   * Replaces the schema with the provided schema and update provided schema files.
   *
   * @param schema
   *            The new schema to use
   * @param modifiedSchemaFiles
   *            The set of schema files to update
   * @param alertGenerator
   *            The generator to use for alerts
   * @throws DirectoryException
   *            If an error occurs during update of schema or schema files
   */
  public void updateSchemaAndSchemaFiles(Schema schema, TreeSet<String> modifiedSchemaFiles,
      AlertGenerator alertGenerator) throws DirectoryException
  {
    exclusiveLock.lock();
    try
    {
      switchSchema(schema);
      new SchemaWriter().updateSchemaFiles(schemaNG, getExtraAttributes(), modifiedSchemaFiles, alertGenerator);
    }
    finally
    {
      exclusiveLock.unlock();
    }
  }
  /**
   * Updates the schema option  if the new value differs from the old value.
   *
   * @param <T> the schema option's type
opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java
@@ -11,25 +11,20 @@
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 2016 ForgeRock AS.
 * Copyright 2006-2010 Sun Microsystems, Inc.
 * Portions Copyright 2011-2016 ForgeRock AS.
 */
package org.opends.server.types;
import static org.opends.messages.BackendMessages.ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.messages.BackendMessages.ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION;
import static org.opends.messages.BackendMessages.ERR_SCHEMA_PARSE_LINE;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.messages.BackendMessages.ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES;
import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
import static org.forgerock.opendj.ldap.ModificationType.ADD;
import static org.forgerock.opendj.ldap.ModificationType.DELETE;
import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.util.CollectionUtils.*;
import static org.opends.server.util.SchemaUtils.getElementSchemaFile;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.util.StaticUtils.toLowerCase;
import static org.opends.server.util.StaticUtils.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -39,22 +34,43 @@
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.AVA;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.CoreSchema;
import org.forgerock.opendj.ldap.schema.DITStructureRule;
import org.forgerock.opendj.ldap.schema.ObjectClass;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.SchemaElement;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.opends.server.api.AlertGenerator;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SchemaConfigManager;
import org.opends.server.util.Base64;
import org.opends.server.util.BuildVersion;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.SchemaUtils;
/**
@@ -201,6 +217,8 @@
   * Identify any differences that may exist between the concatenated schema file from the last
   * online modification and the current schema files. If there are any differences, then they
   * should be from making changes to the schema files with the server offline.
   *
   * @throws InitializationException
   */
  public static void updateConcatenatedSchema() throws InitializationException
  {
@@ -483,4 +501,564 @@
      writer.newLine();
    }
  }
  /**
   * Rewrites all schema files defined in the provided list of modified files with the provided
   * schema.
   *
   * @param newSchema
   *          The new schema that should be used.
   * @param extraAttributes
   *          The extra attributes to write in user schema file.
   * @param modifiedSchemaFiles
   *          The list of files that should be modified.
   * @param alertGenerator
   *          The alert generator.
   * @throws DirectoryException
   *           When the new file cannot be written.
   */
  public void updateSchemaFiles(Schema newSchema, Collection<Attribute> extraAttributes,
      TreeSet<String> modifiedSchemaFiles, AlertGenerator alertGenerator)
          throws DirectoryException
  {
    // 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<>();
    try
    {
      for (String schemaFile : modifiedSchemaFiles)
      {
        File tempSchemaFile = writeTempSchemaFile(newSchema, extraAttributes, schemaFile);
        tempSchemaFiles.put(schemaFile, tempSchemaFile);
      }
      installSchemaFiles(alertGenerator, tempSchemaFiles);
    }
    catch (DirectoryException de)
    {
      logger.traceException(de);
      throw de;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage message =
          ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_SCHEMA.get(getExceptionMessage(e));
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
    }
    finally
    {
      cleanUpTempSchemaFiles(tempSchemaFiles);
    }
    // Create a single file with all of the concatenated schema information
    // that we can use on startup to detect whether the schema files have been
    // edited with the server offline.
    writeConcatenatedSchema();
  }
  /**
   * Creates an empty entry that may be used as the basis for a new schema file.
   *
   * @return An empty entry that may be used as the basis for a new schema file.
   */
  private org.opends.server.types.Entry createEmptySchemaEntry()
  {
    Map<ObjectClass,String> objectClasses = new LinkedHashMap<>();
    objectClasses.put(CoreSchema.getTopObjectClass(), OC_TOP);
    objectClasses.put(DirectoryServer.getSchema().getObjectClass(OC_LDAP_SUBENTRY_LC), OC_LDAP_SUBENTRY);
    objectClasses.put(DirectoryServer.getSchema().getObjectClass(OC_SUBSCHEMA), OC_SUBSCHEMA);
    Map<AttributeType,List<Attribute>> userAttributes = new LinkedHashMap<>();
    Map<AttributeType,List<Attribute>> operationalAttributes = new LinkedHashMap<>();
    DN  dn  = DirectoryServer.getSchemaDN();
    for (AVA ava : dn.rdn())
    {
      AttributeType type = ava.getAttributeType();
      Map<AttributeType, List<Attribute>> attrs = type.isOperational() ? operationalAttributes : userAttributes;
      attrs.put(type, newLinkedList(Attributes.create(type, ava.getAttributeValue())));
    }
    return new org.opends.server.types.Entry(dn, objectClasses,  userAttributes, operationalAttributes);
  }
  /**
   * Writes a temporary version of the specified schema file.
   *
   * @param schema
   *          The schema from which to take the definitions to be
   * @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, Collection<Attribute> extraAttributes, String schemaFile)
          throws DirectoryException, IOException, LDIFException
  {
    org.opends.server.types.Entry schemaEntry = createEmptySchemaEntry();
     /*
     * Add all of the ldap syntax descriptions to the schema entry. We do
     * this only for the real part of the ldapsyntaxes attribute. The real part
     * is read and write to/from the schema files.
     */
    Set<ByteString> values = getValuesForSchemaFile(getCustomSyntaxes(schema), schemaFile);
    addAttribute(schemaEntry, ldapSyntaxesType, values);
    // 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.
    values = getAttributeTypeValuesForSchemaFile(schema, schemaFile);
    addAttribute(schemaEntry, attributeTypesType, values);
    // 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.
    values = getObjectClassValuesForSchemaFile(schema, schemaFile);
    addAttribute(schemaEntry, objectClassesType, values);
    // 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 = getValuesForSchemaFile(schema.getNameForms(), schemaFile);
    addAttribute(schemaEntry, nameFormsType, values);
    // 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 = getValuesForSchemaFile(schema.getDITContentRules(), schemaFile);
    addAttribute(schemaEntry, ditContentRulesType, values);
    // 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.
    values = getDITStructureRuleValuesForSchemaFile(schema, schemaFile);
    addAttribute(schemaEntry, ditStructureRulesType, values);
    // 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 = getValuesForSchemaFile(schema.getMatchingRuleUses(), schemaFile);
    addAttribute(schemaEntry, matchingRuleUsesType, values);
    if (FILE_USER_SCHEMA_ELEMENTS.equals(schemaFile))
    {
      for (Attribute attribute : extraAttributes)
      {
        AttributeType attributeType = attribute.getAttributeDescription().getAttributeType();
        schemaEntry.putAttribute(attributeType, newArrayList(attribute));
      }
    }
    // 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);
    try (LDIFWriter ldifWriter = new LDIFWriter(exportConfig))
    {
      ldifWriter.writeEntry(schemaEntry);
    }
    return tempFile;
  }
  /**
   * Returns custom syntaxes defined by OpenDJ configuration or by users.
   * <p>
   * These are non-standard syntaxes.
   *
   * @param schema
   *          the schema where to extract custom syntaxes from
   * @return custom, non-standard syntaxes
   */
  private Collection<Syntax> getCustomSyntaxes(Schema schema)
  {
    List<Syntax> results = new ArrayList<>();
    for (Syntax syntax : schema.getSyntaxes())
    {
      for (String propertyName : syntax.getExtraProperties().keySet())
      {
        if ("x-subst".equalsIgnoreCase(propertyName)
            || "x-pattern".equalsIgnoreCase(propertyName)
            || "x-enum".equalsIgnoreCase(propertyName)
            || "x-schema-file".equalsIgnoreCase(propertyName))
        {
          results.add(syntax);
          break;
        }
      }
    }
    return results;
  }
  private Set<ByteString> getValuesForSchemaFile(Collection<? extends SchemaElement> schemaElements, String schemaFile)
  {
    Set<ByteString> values = new LinkedHashSet<>();
    for (SchemaElement schemaElement : schemaElements)
    {
      if (schemaFile.equals(getElementSchemaFile(schemaElement)))
      {
        values.add(ByteString.valueOfUtf8(schemaElement.toString()));
      }
    }
    return values;
  }
  private Set<ByteString> getAttributeTypeValuesForSchemaFile(Schema schema,
      String schemaFile) throws DirectoryException
  {
    Set<AttributeType> addedTypes = new HashSet<>();
    Set<ByteString> values = new LinkedHashSet<>();
    for (AttributeType at : schema.getAttributeTypes())
    {
      if (schemaFile.equals(getElementSchemaFile(at)))
      {
        addAttrTypeToSchemaFile(schemaFile, at, values, addedTypes, 0);
      }
    }
    return values;
  }
  private Set<ByteString> getObjectClassValuesForSchemaFile(Schema schema,
      String schemaFile) throws DirectoryException
  {
    Set<ObjectClass> addedClasses = new HashSet<>();
    Set<ByteString> values = new LinkedHashSet<>();
    for (ObjectClass oc : schema.getObjectClasses())
    {
      if (schemaFile.equals(getElementSchemaFile(oc)))
      {
        addObjectClassToSchemaFile(schemaFile, oc, values, addedClasses, 0);
      }
    }
    return values;
  }
  private Set<ByteString> getDITStructureRuleValuesForSchemaFile(Schema schema,
      String schemaFile) throws DirectoryException
  {
    Set<DITStructureRule> addedDSRs = new HashSet<>();
    Set<ByteString> values = new LinkedHashSet<>();
    for (DITStructureRule dsr : schema.getDITStuctureRules())
    {
      if (schemaFile.equals(getElementSchemaFile(dsr)))
      {
        addDITStructureRuleToSchemaFile(schemaFile, dsr, values, addedDSRs, 0);
      }
    }
    return values;
  }
  private void addAttribute(org.opends.server.types.Entry schemaEntry, AttributeType attrType, Set<ByteString> values)
  {
    if (!values.isEmpty())
    {
      AttributeBuilder builder = new AttributeBuilder(attrType);
      builder.addAll(values);
      schemaEntry.putAttribute(attrType, newArrayList(builder.toAttribute()));
    }
  }
  /**
   * 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(String schemaFile, AttributeType attributeType, Set<ByteString> values,
      Set<AttributeType> addedTypes, int depth) throws DirectoryException
  {
    if (depth > 20)
    {
      LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_AT.get(
          attributeType.getNameOrOID());
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }
    if (addedTypes.contains(attributeType))
    {
      return;
    }
    AttributeType superiorType = attributeType.getSuperiorType();
    if (superiorType != null &&
        schemaFile.equals(getElementSchemaFile(attributeType)) &&
        !addedTypes.contains(superiorType))
    {
      addAttrTypeToSchemaFile(schemaFile, superiorType, values, addedTypes, depth+1);
    }
    values.add(ByteString.valueOfUtf8(attributeType.toString()));
    addedTypes.add(attributeType);
  }
  /**
   * Adds the definition for the specified objectclass to the provided set of attribute values,
   * recursively adding superior classes as appropriate.
   *
   * @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(String schemaFile, ObjectClass objectClass, Set<ByteString> values,
      Set<ObjectClass> addedClasses, int depth) throws DirectoryException
  {
    if (depth > 20)
    {
      LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_OC.get(
          objectClass.getNameOrOID());
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }
    if (addedClasses.contains(objectClass))
    {
      return;
    }
    for(ObjectClass superiorClass : objectClass.getSuperiorClasses())
    {
      if (schemaFile.equals(getElementSchemaFile(superiorClass)) &&
          !addedClasses.contains(superiorClass))
      {
        addObjectClassToSchemaFile(schemaFile, superiorClass, values,
                                   addedClasses, depth+1);
      }
    }
    values.add(ByteString.valueOfUtf8(objectClass.toString()));
    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(String schemaFile, DITStructureRule ditStructureRule,
      Set<ByteString> values, Set<DITStructureRule> addedDSRs, int depth) throws DirectoryException
  {
    if (depth > 20)
    {
      LocalizableMessage message = ERR_SCHEMA_MODIFY_CIRCULAR_REFERENCE_DSR.get(
          ditStructureRule.getNameOrRuleID());
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message);
    }
    if (addedDSRs.contains(ditStructureRule))
    {
      return;
    }
    for (DITStructureRule dsr : ditStructureRule.getSuperiorRules())
    {
      if (schemaFile.equals(getElementSchemaFile(dsr)) && !addedDSRs.contains(dsr))
      {
        addDITStructureRuleToSchemaFile(schemaFile, dsr, values,
                                        addedDSRs, depth+1);
      }
    }
    values.add(ByteString.valueOfUtf8(ditStructureRule.toString()));
    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(AlertGenerator alertGenerator, HashMap<String,File> tempSchemaFiles)
          throws DirectoryException
  {
    // 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<>();
    ArrayList<File> tempFileList      = new ArrayList<>();
    ArrayList<File> origFileList      = new ArrayList<>();
    File schemaInstanceDir =
      new File(SchemaConfigManager.getSchemaDirectoryPath());
    for (String name : tempSchemaFiles.keySet())
    {
      installedFileList.add(new File(schemaInstanceDir, name));
      tempFileList.add(tempSchemaFiles.get(name));
      origFileList.add(new File(schemaInstanceDir, 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())
        {
          Files.copy(installedFile.toPath(), origFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        }
      }
    }
    catch (Exception e)
    {
      logger.traceException(e);
      boolean allCleaned = true;
      for (File f : origFileList)
      {
        try
        {
          if (f.exists() && !f.delete())
          {
            allCleaned = false;
          }
        }
        catch (Exception e2)
        {
          logger.traceException(e2);
          allCleaned = false;
        }
      }
      LocalizableMessage message;
      if (allCleaned)
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_CLEANED.get(getExceptionMessage(e));
      }
      else
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_ORIG_FILES_NOT_CLEANED.get(getExceptionMessage(e));
        DirectoryServer.sendAlertNotification(alertGenerator, ALERT_TYPE_CANNOT_COPY_SCHEMA_FILES, message);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, 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);
        Files.copy(tempFile.toPath(), installedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
      }
    }
    catch (Exception e)
    {
      logger.traceException(e);
      deleteFiles(installedFileList);
      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() && !origFile.renameTo(installedFile))
          {
            allRestored = false;
          }
        }
        catch (Exception e2)
        {
          logger.traceException(e2);
          allRestored = false;
        }
      }
      LocalizableMessage message;
      if (allRestored)
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_RESTORED.get(getExceptionMessage(e));
      }
      else
      {
        message = ERR_SCHEMA_MODIFY_CANNOT_WRITE_NEW_FILES_NOT_RESTORED.get(getExceptionMessage(e));
        DirectoryServer.sendAlertNotification(alertGenerator, ALERT_TYPE_CANNOT_WRITE_NEW_SCHEMA_FILES, message);
      }
      throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e);
    }
    deleteFiles(origFileList);
    deleteFiles(tempFileList);
  }
  /**
   * 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)
  {
    deleteFiles(tempSchemaFiles.values());
  }
}
opendj-server-legacy/src/main/java/org/opends/server/util/StaticUtils.java
@@ -1768,6 +1768,29 @@
    return false;
  }
  /**
   * Deletes all provided files.
   * <p>
   * Does not handle directories.
   *
   * @param files
   *            The files to delete.
   * @return {@code true} if deletion is successful for all files, false otherwise
   */
  public static boolean deleteFiles(Iterable<File> files)
  {
    boolean allDeleted = true;
    if (files != null)
    {
      for (File f : files)
      {
          if (!f.isDirectory())
            allDeleted = f.delete() && allDeleted;
          }
      }
    return allDeleted;
  }
  /**