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

Nicolas Capponi
09.38.2016 9d44e6062ba56524e5d7738808044f00349d6738
OPENDJ-3089 OPENDJ-1237 Move code from Schema and SchemaBackend to SchemaUtils and new class SchemaWriter

- create a new class SchemaWriter to handle writing of schema files (move
code from SchemaBackend and Schema classes)
- move static code from SchemaHandler to SchemaUtils
1 files added
6 files modified
1518 ■■■■ changed files
opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java 158 ●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java 239 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java 6 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java 5 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/Schema.java 374 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java 486 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/util/SchemaUtils.java 250 ●●●●● patch | view | raw | blame | history
opendj-server-legacy/src/main/java/org/opends/server/backends/SchemaBackend.java
@@ -42,7 +42,6 @@
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -104,13 +103,13 @@
import org.opends.server.types.Modification;
import org.opends.server.types.Privilege;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.Schema;
import org.opends.server.types.SchemaWriter;
import org.opends.server.types.SearchFilter;
import org.opends.server.util.BackupManager;
import org.opends.server.util.BuildVersion;
import org.opends.server.util.LDIFException;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.LDIFWriter;
import org.opends.server.util.SchemaUtils;
import org.opends.server.util.StaticUtils;
/**
@@ -241,109 +240,12 @@
      }
    }
    updateConcatenatedSchema();
    SchemaWriter.updateConcatenatedSchema();
    // Register with the Directory Server as a configurable component.
    currentConfig.addSchemaChangeListener(this);
  }
  /**
   * Updates the concatenated schema if changes are detected in the current schema files.
   * <p>
   * 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.
   */
  private void updateConcatenatedSchema() throws InitializationException
  {
    try
    {
      // First, generate lists of elements from the current schema.
      Set<String> newATs  = new LinkedHashSet<>();
      Set<String> newOCs  = new LinkedHashSet<>();
      Set<String> newNFs  = new LinkedHashSet<>();
      Set<String> newDCRs = new LinkedHashSet<>();
      Set<String> newDSRs = new LinkedHashSet<>();
      Set<String> newMRUs = new LinkedHashSet<>();
      Set<String> newLSs = new LinkedHashSet<>();
      Schema.genConcatenatedSchema(newATs, newOCs, newNFs, newDCRs, newDSRs, newMRUs, newLSs);
      // Next, generate lists of elements from the previous concatenated schema.
      // If there isn't a previous concatenated schema, then use the base
      // schema for the current revision.
      File concatFile = getConcatFile();
      Set<String> oldATs  = new LinkedHashSet<>();
      Set<String> oldOCs  = new LinkedHashSet<>();
      Set<String> oldNFs  = new LinkedHashSet<>();
      Set<String> oldDCRs = new LinkedHashSet<>();
      Set<String> oldDSRs = new LinkedHashSet<>();
      Set<String> oldMRUs = new LinkedHashSet<>();
      Set<String> oldLSs = new LinkedHashSet<>();
      Schema.readConcatenatedSchema(concatFile, oldATs, oldOCs, oldNFs,
                                    oldDCRs, oldDSRs, oldMRUs,oldLSs);
      // Create a list of modifications and add any differences between the old
      // and new schema into them.
      List<Modification> mods = new LinkedList<>();
      Schema.compareConcatenatedSchema(oldATs, newATs, attributeTypesType, mods);
      Schema.compareConcatenatedSchema(oldOCs, newOCs, objectClassesType, mods);
      Schema.compareConcatenatedSchema(oldNFs, newNFs, nameFormsType, mods);
      Schema.compareConcatenatedSchema(oldDCRs, newDCRs, ditContentRulesType, mods);
      Schema.compareConcatenatedSchema(oldDSRs, newDSRs, ditStructureRulesType, mods);
      Schema.compareConcatenatedSchema(oldMRUs, newMRUs, matchingRuleUsesType, mods);
      Schema.compareConcatenatedSchema(oldLSs, newLSs, ldapSyntaxesType, mods);
      if (! mods.isEmpty())
      {
        // TODO : Raise an alert notification.
        DirectoryServer.setOfflineSchemaChanges(mods);
        // Write a new concatenated schema file with the most recent information
        // so we don't re-find these same changes on the next startup.
        Schema.writeConcatenatedSchema();
      }
    }
    catch (InitializationException ie)
    {
      throw ie;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      logger.error(ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES, getExceptionMessage(e));
    }
  }
  private File getConcatFile() throws InitializationException
  {
    File configDirectory  = new File(DirectoryServer.getConfigFile()).getParentFile();
    File upgradeDirectory = new File(configDirectory, "upgrade");
    File concatFile       = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
    if (concatFile.exists())
    {
      return concatFile.getAbsoluteFile();
    }
    String fileName = SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION + BuildVersion.instanceVersion().getRevision();
    concatFile = new File(upgradeDirectory, fileName);
    if (concatFile.exists())
    {
      return concatFile.getAbsoluteFile();
    }
    String runningUnitTestsStr = System.getProperty(PROPERTY_RUNNING_UNIT_TESTS);
    if ("true".equalsIgnoreCase(runningUnitTestsStr))
    {
      Schema.writeConcatenatedSchema();
      concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
      return concatFile.getAbsoluteFile();
    }
    throw new InitializationException(ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE.get(
        upgradeDirectory.getAbsolutePath(), SCHEMA_CONCAT_FILE_NAME, concatFile.getName()));
  }
  @Override
  public void closeBackend()
  {
@@ -949,7 +851,7 @@
    // 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.
    Schema.writeConcatenatedSchema();
    SchemaWriter.writeConcatenatedSchema();
  }
  /**
@@ -973,7 +875,7 @@
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaHandler.parseAttributeTypeOID(definition);
    String oid = SchemaUtils.parseAttributeTypeOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasAttributeType(oid))
    {
@@ -994,10 +896,10 @@
  private String completeDefinitionWhenAddingSchemaElement(String definition, Set<String> modifiedSchemaFiles)
      throws DirectoryException
  {
    String givenSchemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
    String givenSchemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
    String finalSchemaFile = givenSchemaFile == null ? FILE_USER_SCHEMA_ELEMENTS : givenSchemaFile;
    modifiedSchemaFiles.add(finalSchemaFile);
    return SchemaHandler.addSchemaFileToElementDefinitionIfAbsent(definition, finalSchemaFile);
    return SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(definition, finalSchemaFile);
  }
  /**
@@ -1009,7 +911,7 @@
  private String completeDefinitionWhenReplacingSchemaElement(String definition, SchemaElement existingElement,
      Set<String> modifiedSchemaFiles) throws DirectoryException
  {
    String givenSchemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
    String givenSchemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
    String oldSchemaFile = getElementSchemaFile(existingElement);
    if (givenSchemaFile == null)
@@ -1019,7 +921,7 @@
        oldSchemaFile = FILE_USER_SCHEMA_ELEMENTS;
      }
      modifiedSchemaFiles.add(oldSchemaFile);
      return SchemaHandler.addSchemaFileToElementDefinitionIfAbsent(definition, oldSchemaFile);
      return SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(definition, oldSchemaFile);
    }
    else if (oldSchemaFile == null || oldSchemaFile.equals(givenSchemaFile))
    {
@@ -1064,7 +966,7 @@
      int currentPosition, Set<String> modifiedSchemaFiles) throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    String atOID = SchemaHandler.parseAttributeTypeOID(definition);
    String atOID = SchemaUtils.parseAttributeTypeOID(definition);
    if (!currentSchema.hasAttributeType(atOID))
    {
@@ -1090,7 +992,7 @@
      {
        try
        {
          String oid = SchemaHandler.parseAttributeTypeOID(v.toString());
          String oid = SchemaUtils.parseAttributeTypeOID(v.toString());
          if (atOID.equals(oid))
          {
            // We found a match where the attribute type is added back later,
@@ -1131,7 +1033,7 @@
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaHandler.parseObjectClassOID(definition);
    String oid = SchemaUtils.parseObjectClassOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasObjectClass(oid))
    {
@@ -1178,7 +1080,7 @@
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    String ocOID = SchemaHandler.parseObjectClassOID(definition);
    String ocOID = SchemaUtils.parseObjectClassOID(definition);
    if (!currentSchema.hasObjectClass(ocOID))
    {
@@ -1205,7 +1107,7 @@
        String oid;
        try
        {
          oid = SchemaHandler.parseObjectClassOID(v.toString());
          oid = SchemaUtils.parseObjectClassOID(v.toString());
        }
        catch (DirectoryException de)
        {
@@ -1247,7 +1149,7 @@
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaHandler.parseNameFormOID(definition);
    String oid = SchemaUtils.parseNameFormOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasNameForm(oid))
    {
@@ -1293,7 +1195,7 @@
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    String nfOID = SchemaHandler.parseNameFormOID(definition);
    String nfOID = SchemaUtils.parseNameFormOID(definition);
    if (!currentSchema.hasNameForm(nfOID))
    {
@@ -1319,7 +1221,7 @@
      {
        try
        {
          String oid = SchemaHandler.parseNameFormOID(v.toString());
          String oid = SchemaUtils.parseNameFormOID(v.toString());
          if (nfOID.equals(oid))
          {
            // We found a match where the name form is added back later, so we
@@ -1361,7 +1263,7 @@
      Set<String> modifiedSchemaFiles) throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaHandler.parseDITContentRuleOID(definition);
    String oid = SchemaUtils.parseDITContentRuleOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasDITContentRule(oid))
    {
@@ -1401,7 +1303,7 @@
      SchemaBuilder newSchemaBuilder, Set<String> modifiedSchemaFiles) throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    String ruleOid = SchemaHandler.parseDITContentRuleOID(definition);
    String ruleOid = SchemaUtils.parseDITContentRuleOID(definition);
    if (! currentSchema.hasDITContentRule(ruleOid))
    {
@@ -1439,7 +1341,7 @@
      throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    int ruleId = SchemaHandler.parseRuleID(definition);
    int ruleId = SchemaUtils.parseRuleID(definition);
    final String finalDefinition;
    if (!currentSchema.hasDITStructureRule(ruleId))
    {
@@ -1487,7 +1389,7 @@
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    int ruleID = SchemaHandler.parseRuleID(definition);
    int ruleID = SchemaUtils.parseRuleID(definition);
    if (!currentSchema.hasDITStructureRule(ruleID))
    {
@@ -1511,7 +1413,7 @@
      for (ByteString v : a)
      {
        int id = SchemaHandler.parseRuleID(v.toString());
        int id = SchemaUtils.parseRuleID(v.toString());
        if (ruleID == id)
        {
          // We found a match where the DIT structure rule is added back later,
@@ -1547,7 +1449,7 @@
      throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = schemaHandler.getSchema();
    String oid = SchemaHandler.parseMatchingRuleUseOID(definition);
    String oid = SchemaUtils.parseMatchingRuleUseOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasMatchingRuleUse(oid))
    {
@@ -1589,7 +1491,7 @@
          throws DirectoryException
  {
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    String mruOid = SchemaHandler.parseMatchingRuleUseOID(definition);
    String mruOid = SchemaUtils.parseMatchingRuleUseOID(definition);
    if (!currentSchema.hasMatchingRuleUse(mruOid))
    {
@@ -1628,7 +1530,7 @@
    // 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();
    String oid = SchemaHandler.parseSyntaxOID(definition);
    String oid = SchemaUtils.parseSyntaxOID(definition);
    final String finalDefinition;
    if (!currentSchema.hasSyntax(oid))
    {
@@ -1652,7 +1554,7 @@
     * hence never deleted.
     */
    org.forgerock.opendj.ldap.schema.Schema currentSchema = newSchemaBuilder.toSchema();
    String oid = SchemaHandler.parseSyntaxOID(definition);
    String oid = SchemaUtils.parseSyntaxOID(definition);
    if (!currentSchema.hasSyntax(oid))
    {
@@ -2440,13 +2342,13 @@
      for (ByteString v : a)
      {
        String definition = v.toString();
        String schemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
        String schemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
        if (is02ConfigLdif(schemaFile))
        {
          continue;
        }
        String oid = SchemaHandler.parseAttributeTypeOID(definition);
        String oid = SchemaUtils.parseAttributeTypeOID(definition);
        oidList.add(oid);
        try
        {
@@ -2492,12 +2394,12 @@
        // It IS important here to allow the unknown elements that could
        // appear in the new config schema.
        String definition = v.toString();
        String schemaFile = SchemaHandler.parseSchemaFileFromElementDefinition(definition);
        String schemaFile = SchemaUtils.parseSchemaFileFromElementDefinition(definition);
        if (is02ConfigLdif(schemaFile))
        {
          continue;
        }
        String oid = SchemaHandler.parseObjectClassOID(definition);
        String oid = SchemaUtils.parseObjectClassOID(definition);
        oidList.add(oid);
        try
        {
opendj-server-legacy/src/main/java/org/opends/server/core/SchemaHandler.java
@@ -15,14 +15,6 @@
 */
package org.opends.server.core;
import static org.opends.messages.SchemaMessages.ERR_PARSING_LDAP_SYNTAX_OID;
import static org.opends.messages.SchemaMessages.ERR_PARSING_MATCHING_RULE_USE_OID;
import static org.opends.messages.SchemaMessages.ERR_PARSING_DIT_STRUCTURE_RULE_RULEID;
import static org.opends.messages.SchemaMessages.ERR_PARSING_DIT_CONTENT_RULE_OID;
import static org.opends.messages.SchemaMessages.ERR_PARSING_NAME_FORM_OID;
import static org.opends.messages.SchemaMessages.ERR_PARSING_OBJECTCLASS_OID;
import static org.opends.messages.SchemaMessages.ERR_PARSING_ATTRIBUTE_TYPE_OID;
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;
@@ -49,7 +41,6 @@
import org.opends.server.schema.AciSyntax;
import org.opends.server.schema.SubtreeSpecificationSyntax;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.ClassPropertyDefinition;
import org.forgerock.opendj.config.server.ConfigException;
@@ -83,8 +74,6 @@
import org.opends.server.util.ActivateOnceSDKSchemaIsUsed;
import org.opends.server.util.StaticUtils;
import com.forgerock.opendj.util.SubstringReader;
/**
 * Responsible for loading the server schema.
 * <p>
@@ -294,88 +283,6 @@
    return extraAttributes.values();
  }
  /**
   * Adds the provided schema file to the provided schema element definition.
   *
   * @param definition
   *            The schema element definition
   * @param schemaFile
   *            The name of the schema file to include in the definition
   * @return  The definition string of the element
   *          including the X-SCHEMA-FILE extension.
   */
  public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile)
  {
    if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME))
    {
      int pos = definition.lastIndexOf(')');
      return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
    }
    return definition;
  }
  /**
   * Parses the schema file (value of X-SCHEMA-FILE extension) from the provided schema element
   * definition.
   * <p>
   * It expects a single value for the X-SCHEMA-FILE extension, e.g.:
   * "X-SCHEMA-FILE '99-user.ldif'", as there is no sensible meaning for multiple values.
   *
   * @param definition
   *          The definition of a schema element
   * @return the value of the schema file or {@code null} if the X-SCHEMA-FILE extension is not
   *         present in the definition
   * @throws DirectoryException
   *            If an error occurs while parsing the schema element definition
   */
  public static String parseSchemaFileFromElementDefinition(String definition) throws DirectoryException
  {
    int pos = definition.lastIndexOf(SCHEMA_PROPERTY_FILENAME);
    if (pos == -1)
    {
      return null;
    }
    SubstringReader reader = new SubstringReader(definition);
    reader.read(pos + SCHEMA_PROPERTY_FILENAME.length());
    int length = 0;
    reader.skipWhitespaces();
    reader.mark();
    try
    {
      // Accept both a quoted value or an unquoted value
      char c = reader.read();
      if (c == '\'')
      {
        reader.mark();
        // Parse until the closing quote.
        while (reader.read() != '\'')
        {
          length++;
        }
      }
      else
      {
        // Parse until the next space.
        do
        {
          length++;
        }
        while (reader.read() != ' ');
      }
      reader.reset();
      return reader.read(length);
    }
    catch (final StringIndexOutOfBoundsException e)
    {
      // TODO : write the correct message = Error when trying to parse the schema file from a schema
      // element definition
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, LocalizableMessage.raw(""));
    }
  }
  /** Takes an exclusive lock on the schema. */
  public void exclusiveLock()
  {
@@ -765,150 +672,4 @@
     */
    Schema update(SchemaBuilder builder) throws DirectoryException;
  }
  /**
   * Returns the OID from the provided attribute type definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of an attribute type, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseAttributeTypeOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_ATTRIBUTE_TYPE_OID);
  }
  /**
   * Returns the OID from the provided object class definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a object class, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseObjectClassOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_OBJECTCLASS_OID);
  }
  /**
   * Returns the OID from the provided name form definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a name form, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseNameFormOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_NAME_FORM_OID);
  }
  /**
   * Returns the OID from the provided DIT content rule definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a DIT content rule, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseDITContentRuleOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_DIT_CONTENT_RULE_OID);
  }
  /**
   * Returns the ruleID from the provided DIT structure rule definition, assuming the definition is
   * valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a DIT structure rule, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static int parseRuleID(String definition) throws DirectoryException
  {
    // Reuse code of parseOID, even though this is not an OID
    return Integer.parseInt(parseOID(definition, ERR_PARSING_DIT_STRUCTURE_RULE_RULEID));
  }
  /**
   * Returns the OID from the provided matching rule use definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a matching rule use, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseMatchingRuleUseOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_MATCHING_RULE_USE_OID);
  }
  /**
   * Returns the OID from the provided syntax definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a syntax, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseSyntaxOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_LDAP_SYNTAX_OID);
  }
  private static String parseOID(String definition, Arg1<Object> parsingErrorMsg) throws DirectoryException
  {
    try
    {
      int pos = 0;
      int length = definition.length();
      // Skip over any leading whitespace.
      while (pos < length && (definition.charAt(pos) == ' '))
      {
        pos++;
      }
      // Skip the open parenthesis.
      pos++;
      // Skip over any spaces immediately following the opening parenthesis.
      while (pos < length && definition.charAt(pos) == ' ')
      {
        pos++;
      }
      // The next set of characters must be the OID.
      int oidStartPos = pos;
      while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')')
      {
        pos++;
      }
      return definition.substring(oidStartPos, pos);
    }
    catch (IndexOutOfBoundsException e)
    {
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, parsingErrorMsg.get(definition), e);
    }
  }
}
opendj-server-legacy/src/main/java/org/opends/server/tasks/AddSchemaFileTask.java
@@ -44,6 +44,8 @@
import org.opends.server.types.Operation;
import org.opends.server.types.Privilege;
import org.opends.server.types.Schema;
import org.opends.server.types.SchemaWriter;
import org.opends.server.util.SchemaUtils;
import static org.opends.messages.TaskMessages.*;
import static org.opends.server.config.ConfigConstants.*;
@@ -181,7 +183,7 @@
            AttributeBuilder builder = new AttributeBuilder(a.getAttributeDescription());
            for (ByteString v : a)
            {
              builder.add(Schema.addSchemaFileToElementDefinitionIfAbsent(v.toString(), schemaFile));
              builder.add(SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(v.toString(), schemaFile));
            }
            mods.add(new Modification(m.getModificationType(), builder.toAttribute()));
@@ -213,7 +215,7 @@
          }
        }
        Schema.writeConcatenatedSchema();
        SchemaWriter.writeConcatenatedSchema();
      }
      schema.setYoungestModificationTime(System.currentTimeMillis());
opendj-server-legacy/src/main/java/org/opends/server/tools/upgrade/UpgradeTasks.java
@@ -15,17 +15,17 @@
 */
package org.opends.server.tools.upgrade;
import static org.opends.server.util.SchemaUtils.addSchemaFileToElementDefinitionIfAbsent;
import static java.nio.charset.StandardCharsets.*;
import static java.nio.file.StandardOpenOption.*;
import static javax.security.auth.callback.ConfirmationCallback.NO;
import static javax.security.auth.callback.ConfirmationCallback.YES;
import static javax.security.auth.callback.TextOutputCallback.*;
import static org.forgerock.util.Utils.joinAsString;
import static org.opends.messages.ToolMessages.*;
import static org.opends.server.tools.upgrade.FileManager.copyRecursively;
import static org.opends.server.tools.upgrade.UpgradeUtils.*;
import static org.opends.server.types.Schema.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.BufferedWriter;
@@ -67,6 +67,7 @@
import org.opends.server.tools.RebuildIndex;
import org.opends.server.util.BuildVersion;
import org.opends.server.util.ChangeOperationType;
import org.opends.server.util.SchemaUtils;
import org.opends.server.util.StaticUtils;
import com.forgerock.opendj.cli.ClientException;
opendj-server-legacy/src/main/java/org/opends/server/types/Schema.java
@@ -16,32 +16,14 @@
 */
package org.opends.server.types;
import static org.forgerock.opendj.ldap.ModificationType.*;
import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
import static org.forgerock.opendj.ldap.schema.SchemaOptions.*;
import static org.opends.messages.BackendMessages.*;
import static org.opends.messages.SchemaMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.ParseException;
import java.util.Collection;
import java.util.HashMap;
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 java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -49,9 +31,6 @@
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
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.ConflictingSchemaElementException;
@@ -68,8 +47,6 @@
import org.forgerock.util.Option;
import org.forgerock.util.Utils;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SchemaConfigManager;
import org.opends.server.util.Base64;
/**
 * This class defines a data structure that holds information about the components of the Directory
@@ -1551,357 +1528,6 @@
    extraAttributes.put(name, attr);
  }
  /**
   * Writes a single file containing all schema element definitions,
   * which can be used on startup to determine whether the schema
   * files were edited with the server offline.
   */
  public static void writeConcatenatedSchema()
  {
    String concatFilePath = null;
    try
    {
      Set<String> attributeTypes = new LinkedHashSet<>();
      Set<String> objectClasses = new LinkedHashSet<>();
      Set<String> nameForms = new LinkedHashSet<>();
      Set<String> ditContentRules = new LinkedHashSet<>();
      Set<String> ditStructureRules = new LinkedHashSet<>();
      Set<String> matchingRuleUses = new LinkedHashSet<>();
      Set<String> ldapSyntaxes = new LinkedHashSet<>();
      genConcatenatedSchema(attributeTypes, objectClasses, nameForms,
                            ditContentRules, ditStructureRules,
                            matchingRuleUses,ldapSyntaxes);
      File configFile = new File(DirectoryServer.getConfigFile());
      File configDirectory = configFile.getParentFile();
      File upgradeDirectory = new File(configDirectory, "upgrade");
      upgradeDirectory.mkdir();
      File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
      concatFilePath = concatFile.getAbsolutePath();
      File tempFile = new File(concatFilePath + ".tmp");
      try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false)))
      {
        writeLines(writer,
            "dn: " + DirectoryServer.getSchemaDN(),
            "objectClass: top",
            "objectClass: ldapSubentry",
            "objectClass: subschema");
        writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes);
        writeLines(writer, ATTR_OBJECTCLASSES, objectClasses);
        writeLines(writer, ATTR_NAME_FORMS, nameForms);
        writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules);
        writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules);
        writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses);
        writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes);
      }
      if (concatFile.exists())
      {
        concatFile.delete();
      }
      tempFile.renameTo(concatFile);
    }
    catch (Exception e)
    {
      logger.traceException(e);
      // This is definitely not ideal, but it's not the end of the
      // world.  The worst that should happen is that the schema
      // changes could potentially be sent to the other servers again
      // when this server is restarted, which shouldn't hurt anything.
      // Still, we should log a warning message.
      logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e));
    }
  }
  private static void writeLines(BufferedWriter writer, String... lines) throws IOException
  {
    for (String line : lines)
    {
      writer.write(line);
      writer.newLine();
    }
  }
  private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException
  {
    for (String line : lines)
    {
      writer.write(beforeColumn);
      writer.write(": ");
      writer.write(line);
      writer.newLine();
    }
  }
  /**
   * Reads the files contained in the schema directory and generates a
   * concatenated view of their contents in the provided sets.
   *
   * @param  attributeTypes     The set into which to place the
   *                            attribute types read from the schema
   *                            files.
   * @param  objectClasses      The set into which to place the object
   *                            classes read from the schema files.
   * @param  nameForms          The set into which to place the name
   *                            forms read from the schema files.
   * @param  ditContentRules    The set into which to place the DIT
   *                            content rules read from the schema
   *                            files.
   * @param  ditStructureRules  The set into which to place the DIT
   *                            structure rules read from the schema
   *                            files.
   * @param  matchingRuleUses   The set into which to place the
   *                            matching rule uses read from the
   *                            schema files.
   * @param ldapSyntaxes The set into which to place the
   *                            ldap syntaxes read from the
   *                            schema files.
   *
   * @throws  IOException  If a problem occurs while reading the
   *                       schema file elements.
   */
  public static void genConcatenatedSchema(
                          Set<String> attributeTypes,
                          Set<String> objectClasses,
                          Set<String> nameForms,
                          Set<String> ditContentRules,
                          Set<String> ditStructureRules,
                          Set<String> matchingRuleUses,
                          Set<String> ldapSyntaxes)
          throws IOException
  {
    // Get a sorted list of the files in the schema directory.
    TreeSet<File> schemaFiles = new TreeSet<>();
    String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath();
    final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter();
    for (File f : new File(schemaDirectory).listFiles(filter))
    {
      if (f.isFile())
      {
        schemaFiles.add(f);
      }
    }
    // Open each of the files in order and read the elements that they
    // contain, appending them to the appropriate lists.
    for (File f : schemaFiles)
    {
      List<StringBuilder> lines = readSchemaElementsFromLdif(f);
      // Iterate through each line in the list.  Find the colon and
      // get the attribute name at the beginning.  If it's something
      // that we don't recognize, then skip it.  Otherwise, add the
      // X-SCHEMA-FILE extension and add it to the appropriate schema
      // element list.
      for (StringBuilder buffer : lines)
      {
        String line = buffer.toString().trim();
        parseSchemaLine(line, f.getName(), attributeTypes, objectClasses,
            nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
            ldapSyntaxes);
      }
    }
  }
  private static List<StringBuilder> readSchemaElementsFromLdif(File f) throws IOException, FileNotFoundException
  {
    final LinkedList<StringBuilder> lines = new LinkedList<>();
    try (BufferedReader reader = new BufferedReader(new FileReader(f)))
    {
      String line;
      while ((line = reader.readLine()) != null)
      {
        if (line.startsWith("#") || line.length() == 0)
        {
          continue;
        }
        else if (line.startsWith(" "))
        {
          lines.getLast().append(line.substring(1));
        }
        else
        {
          lines.add(new StringBuilder(line));
        }
      }
    }
    return lines;
  }
  /**
   * Reads data from the specified concatenated schema file into the
   * provided sets.
   *
   * @param  concatSchemaFile   The concatenated schema file to be read.
   * @param  attributeTypes     The set into which to place the attribute types
   *                            read from the concatenated schema file.
   * @param  objectClasses      The set into which to place the object classes
   *                            read from the concatenated schema file.
   * @param  nameForms          The set into which to place the name forms
   *                            read from the concatenated schema file.
   * @param  ditContentRules    The set into which to place the DIT content rules
   *                            read from the concatenated schema file.
   * @param  ditStructureRules  The set into which to place the DIT structure rules
   *                            read from the concatenated schema file.
   * @param  matchingRuleUses   The set into which to place the matching rule
   *                            uses read from the concatenated schema file.
   * @param ldapSyntaxes        The set into which to place the ldap syntaxes
   *                            read from the concatenated schema file.
   *
   * @throws  IOException  If a problem occurs while reading the
   *                       schema file elements.
   */
  public static void readConcatenatedSchema(File concatSchemaFile,
                          Set<String> attributeTypes,
                          Set<String> objectClasses,
                          Set<String> nameForms,
                          Set<String> ditContentRules,
                          Set<String> ditStructureRules,
                          Set<String> matchingRuleUses,
                          Set<String> ldapSyntaxes)
          throws IOException
  {
    try (BufferedReader reader = new BufferedReader(new FileReader(concatSchemaFile)))
    {
      String line;
      while ((line = reader.readLine()) != null)
      {
        parseSchemaLine(line, null, attributeTypes, objectClasses,
            nameForms, ditContentRules, ditStructureRules, matchingRuleUses,
            ldapSyntaxes);
      }
    }
  }
  private static void parseSchemaLine(String definition, String fileName,
      Set<String> attributeTypes,
      Set<String> objectClasses,
      Set<String> nameForms,
      Set<String> ditContentRules,
      Set<String> ditStructureRules,
      Set<String> matchingRuleUses,
      Set<String> ldapSyntaxes)
  {
    String lowerLine = toLowerCase(definition);
    try
    {
      if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
      {
        addSchemaDefinition(attributeTypes, definition, ATTR_ATTRIBUTE_TYPES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
      {
        addSchemaDefinition(objectClasses, definition, ATTR_OBJECTCLASSES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
      {
        addSchemaDefinition(nameForms, definition, ATTR_NAME_FORMS_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
      {
        addSchemaDefinition(ditContentRules, definition, ATTR_DIT_CONTENT_RULES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
      {
        addSchemaDefinition(ditStructureRules, definition, ATTR_DIT_STRUCTURE_RULES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
      {
        addSchemaDefinition(matchingRuleUses, definition, ATTR_MATCHING_RULE_USE_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC))
      {
        addSchemaDefinition(ldapSyntaxes, definition, ATTR_LDAP_SYNTAXES_LC, fileName);
      }
    } catch (ParseException pe)
    {
      logger.error(ERR_SCHEMA_PARSE_LINE.get(definition, pe.getLocalizedMessage()));
    }
  }
  private static void addSchemaDefinition(Set<String> definitions, String line, String attrName, String fileName)
      throws ParseException
  {
    definitions.add(getSchemaDefinition(line.substring(attrName.length()), fileName));
  }
  private static String getSchemaDefinition(String definition, String schemaFile) throws ParseException
  {
    if (definition.startsWith("::"))
    {
      // See OPENDJ-2792: the definition of the ds-cfg-csv-delimiter-char attribute type
      // had a space accidentally added after the closing parenthesis.
      // This was unfortunately interpreted as base64
      definition = ByteString.wrap(Base64.decode(definition.substring(2).trim())).toString();
    }
    else if (definition.startsWith(":"))
    {
      definition = definition.substring(1).trim();
    }
    else
    {
      throw new ParseException(ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION.get().toString(), 0);
    }
    return addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile);
  }
  /**
   * Compares the provided sets of schema element definitions and
   * writes any differences found into the given list of
   * modifications.
   *
   * @param  oldElements  The set of elements of the specified type
   *                      read from the previous concatenated schema
   *                      files.
   * @param  newElements  The set of elements of the specified type
   *                      read from the server's current schema.
   * @param  elementType  The attribute type associated with the
   *                      schema element being compared.
   * @param  mods         The list of modifications into which any
   *                      identified differences should be written.
   */
  public static void compareConcatenatedSchema(
                          Set<String> oldElements,
                          Set<String> newElements,
                          AttributeType elementType,
                          List<Modification> mods)
  {
    AttributeBuilder builder = new AttributeBuilder(elementType);
    addModification(mods, DELETE, oldElements, newElements, builder);
    builder.setAttributeDescription(AttributeDescription.create(elementType));
    addModification(mods, ADD, newElements, oldElements, builder);
  }
  private static void addModification(List<Modification> mods, ModificationType modType, Set<String> included,
      Set<String> excluded, AttributeBuilder builder)
  {
    for (String val : included)
    {
      if (!excluded.contains(val))
      {
        builder.add(val);
      }
    }
    if (!builder.isEmpty())
    {
      mods.add(new Modification(modType, builder.toAttribute()));
    }
  }
  /**
   * Destroys the structures maintained by the schema so that they are
   * no longer usable. This should only be called at the end of the
opendj-server-legacy/src/main/java/org/opends/server/types/SchemaWriter.java
New file
@@ -0,0 +1,486 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions Copyright [year] [name of copyright owner]".
 *
 * Copyright 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.opends.messages.BackendMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.getExceptionMessage;
import static org.opends.server.util.StaticUtils.toLowerCase;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.ParseException;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.schema.AttributeType;
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.SchemaUtils;
/**
 * Provides support to write schema files.
 */
public class SchemaWriter
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  private static final AttributeType attributeTypesType = getAttributeTypesAttributeType();
  private static final AttributeType ditStructureRulesType = getDITStructureRulesAttributeType();
  private static final AttributeType ditContentRulesType = getDITContentRulesAttributeType();
  private static final AttributeType ldapSyntaxesType = getLDAPSyntaxesAttributeType();
  private static final AttributeType matchingRuleUsesType = getMatchingRuleUseAttributeType();
  private static final AttributeType nameFormsType = getNameFormsAttributeType();
  private static final AttributeType objectClassesType = getObjectClassesAttributeType();
  /**
   * Compares the provided sets of schema element definitions and writes any differences found into
   * the given list of modifications.
   *
   * @param oldElements
   *          The set of elements of the specified type read from the previous concatenated schema
   *          files.
   * @param newElements
   *          The set of elements of the specified type read from the server's current schema.
   * @param elementType
   *          The attribute type associated with the schema element being compared.
   * @param mods
   *          The list of modifications into which any identified differences should be written.
   */
  public static void compareConcatenatedSchema(Set<String> oldElements, Set<String> newElements,
      AttributeType elementType, List<Modification> mods)
  {
    AttributeBuilder builder = new AttributeBuilder(elementType);
    addModification(mods, DELETE, oldElements, newElements, builder);
    builder.setAttributeDescription(AttributeDescription.create(elementType));
    addModification(mods, ADD, newElements, oldElements, builder);
  }
  /**
   * Reads the files contained in the schema directory and generates a concatenated view of their
   * contents in the provided sets.
   *
   * @param attributeTypes
   *          The set into which to place the attribute types read from the schema files.
   * @param objectClasses
   *          The set into which to place the object classes read from the schema files.
   * @param nameForms
   *          The set into which to place the name forms read from the schema files.
   * @param ditContentRules
   *          The set into which to place the DIT content rules read from the schema files.
   * @param ditStructureRules
   *          The set into which to place the DIT structure rules read from the schema files.
   * @param matchingRuleUses
   *          The set into which to place the matching rule uses read from the schema files.
   * @param ldapSyntaxes
   *          The set into which to place the ldap syntaxes read from the schema files.
   * @throws IOException
   *           If a problem occurs while reading the schema file elements.
   */
  public static void generateConcatenatedSchema(Set<String> attributeTypes, Set<String> objectClasses,
      Set<String> nameForms, Set<String> ditContentRules, Set<String> ditStructureRules, Set<String> matchingRuleUses,
      Set<String> ldapSyntaxes) throws IOException
  {
    // Get a sorted list of the files in the schema directory.
    TreeSet<File> schemaFiles = new TreeSet<>();
    String schemaDirectory = SchemaConfigManager.getSchemaDirectoryPath();
    final FilenameFilter filter = new SchemaConfigManager.SchemaFileFilter();
    for (File f : new File(schemaDirectory).listFiles(filter))
    {
      if (f.isFile())
      {
        schemaFiles.add(f);
      }
    }
    // Open each of the files in order and read the elements that they
    // contain, appending them to the appropriate lists.
    for (File f : schemaFiles)
    {
      List<StringBuilder> lines = readSchemaElementsFromLdif(f);
      // Iterate through each line in the list. Find the colon and
      // get the attribute name at the beginning. If it's something
      // that we don't recognize, then skip it. Otherwise, add the
      // X-SCHEMA-FILE extension and add it to the appropriate schema
      // element list.
      for (StringBuilder buffer : lines)
      {
        String line = buffer.toString().trim();
        parseSchemaLine(line, f.getName(), attributeTypes, objectClasses, nameForms, ditContentRules,
            ditStructureRules, matchingRuleUses, ldapSyntaxes);
      }
    }
  }
  /**
   * Reads data from the specified concatenated schema file into the provided sets.
   *
   * @param concatSchemaFile
   *          The concatenated schema file to be read.
   * @param attributeTypes
   *          The set into which to place the attribute types read from the concatenated schema
   *          file.
   * @param objectClasses
   *          The set into which to place the object classes read from the concatenated schema file.
   * @param nameForms
   *          The set into which to place the name forms read from the concatenated schema file.
   * @param ditContentRules
   *          The set into which to place the DIT content rules read from the concatenated schema
   *          file.
   * @param ditStructureRules
   *          The set into which to place the DIT structure rules read from the concatenated schema
   *          file.
   * @param matchingRuleUses
   *          The set into which to place the matching rule uses read from the concatenated schema
   *          file.
   * @param ldapSyntaxes
   *          The set into which to place the ldap syntaxes read from the concatenated schema file.
   * @throws IOException
   *           If a problem occurs while reading the schema file elements.
   */
  public static void readConcatenatedSchema(File concatSchemaFile, Set<String> attributeTypes,
      Set<String> objectClasses, Set<String> nameForms, Set<String> ditContentRules, Set<String> ditStructureRules,
      Set<String> matchingRuleUses, Set<String> ldapSyntaxes) throws IOException
  {
    try (BufferedReader reader = new BufferedReader(new FileReader(concatSchemaFile)))
    {
      String line;
      while ((line = reader.readLine()) != null)
      {
        parseSchemaLine(line, null, attributeTypes, objectClasses, nameForms, ditContentRules, ditStructureRules,
            matchingRuleUses, ldapSyntaxes);
      }
    }
  }
  /**
   * Updates the concatenated schema if changes are detected in the current schema files.
   * <p>
   * 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.
   */
  public static void updateConcatenatedSchema() throws InitializationException
  {
    try
    {
      // First, generate lists of elements from the current schema.
      Set<String> newATs = new LinkedHashSet<>();
      Set<String> newOCs = new LinkedHashSet<>();
      Set<String> newNFs = new LinkedHashSet<>();
      Set<String> newDCRs = new LinkedHashSet<>();
      Set<String> newDSRs = new LinkedHashSet<>();
      Set<String> newMRUs = new LinkedHashSet<>();
      Set<String> newLSs = new LinkedHashSet<>();
      generateConcatenatedSchema(newATs, newOCs, newNFs, newDCRs, newDSRs, newMRUs, newLSs);
      // Next, generate lists of elements from the previous concatenated schema.
      // If there isn't a previous concatenated schema, then use the base
      // schema for the current revision.
      File concatFile = getConcatenatedSchemaFile();
      Set<String> oldATs = new LinkedHashSet<>();
      Set<String> oldOCs = new LinkedHashSet<>();
      Set<String> oldNFs = new LinkedHashSet<>();
      Set<String> oldDCRs = new LinkedHashSet<>();
      Set<String> oldDSRs = new LinkedHashSet<>();
      Set<String> oldMRUs = new LinkedHashSet<>();
      Set<String> oldLSs = new LinkedHashSet<>();
      readConcatenatedSchema(concatFile, oldATs, oldOCs, oldNFs, oldDCRs, oldDSRs, oldMRUs, oldLSs);
      // Create a list of modifications and add any differences between the old
      // and new schema into them.
      List<Modification> mods = new LinkedList<>();
      compareConcatenatedSchema(oldATs, newATs, attributeTypesType, mods);
      compareConcatenatedSchema(oldOCs, newOCs, objectClassesType, mods);
      compareConcatenatedSchema(oldNFs, newNFs, nameFormsType, mods);
      compareConcatenatedSchema(oldDCRs, newDCRs, ditContentRulesType, mods);
      compareConcatenatedSchema(oldDSRs, newDSRs, ditStructureRulesType, mods);
      compareConcatenatedSchema(oldMRUs, newMRUs, matchingRuleUsesType, mods);
      compareConcatenatedSchema(oldLSs, newLSs, ldapSyntaxesType, mods);
      if (!mods.isEmpty())
      {
        // TODO : Raise an alert notification.
        DirectoryServer.setOfflineSchemaChanges(mods);
        // Write a new concatenated schema file with the most recent information
        // so we don't re-find these same changes on the next startup.
        writeConcatenatedSchema();
      }
    }
    catch (InitializationException ie)
    {
      throw ie;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      logger.error(ERR_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES, getExceptionMessage(e));
    }
  }
  /**
   * Writes a single file containing all schema element definitions, which can be used on startup to
   * determine whether the schema files were edited with the server offline.
   */
  public static void writeConcatenatedSchema()
  {
    String concatFilePath = null;
    try
    {
      Set<String> attributeTypes = new LinkedHashSet<>();
      Set<String> objectClasses = new LinkedHashSet<>();
      Set<String> nameForms = new LinkedHashSet<>();
      Set<String> ditContentRules = new LinkedHashSet<>();
      Set<String> ditStructureRules = new LinkedHashSet<>();
      Set<String> matchingRuleUses = new LinkedHashSet<>();
      Set<String> ldapSyntaxes = new LinkedHashSet<>();
      generateConcatenatedSchema(attributeTypes, objectClasses, nameForms, ditContentRules, ditStructureRules,
          matchingRuleUses, ldapSyntaxes);
      File configFile = new File(DirectoryServer.getConfigFile());
      File configDirectory = configFile.getParentFile();
      File upgradeDirectory = new File(configDirectory, "upgrade");
      upgradeDirectory.mkdir();
      File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
      concatFilePath = concatFile.getAbsolutePath();
      File tempFile = new File(concatFilePath + ".tmp");
      try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile, false)))
      {
        writeLines(writer, "dn: " + DirectoryServer.getSchemaDN(), "objectClass: top", "objectClass: ldapSubentry",
            "objectClass: subschema");
        writeLines(writer, ATTR_ATTRIBUTE_TYPES, attributeTypes);
        writeLines(writer, ATTR_OBJECTCLASSES, objectClasses);
        writeLines(writer, ATTR_NAME_FORMS, nameForms);
        writeLines(writer, ATTR_DIT_CONTENT_RULES, ditContentRules);
        writeLines(writer, ATTR_DIT_STRUCTURE_RULES, ditStructureRules);
        writeLines(writer, ATTR_MATCHING_RULE_USE, matchingRuleUses);
        writeLines(writer, ATTR_LDAP_SYNTAXES, ldapSyntaxes);
      }
      if (concatFile.exists())
      {
        concatFile.delete();
      }
      tempFile.renameTo(concatFile);
    }
    catch (Exception e)
    {
      logger.traceException(e);
      // This is definitely not ideal, but it's not the end of the
      // world. The worst that should happen is that the schema
      // changes could potentially be sent to the other servers again
      // when this server is restarted, which shouldn't hurt anything.
      // Still, we should log a warning message.
      logger.error(ERR_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE, concatFilePath, getExceptionMessage(e));
    }
  }
  private static void addModification(List<Modification> mods, ModificationType modType, Set<String> included,
      Set<String> excluded, AttributeBuilder builder)
  {
    for (String val : included)
    {
      if (!excluded.contains(val))
      {
        builder.add(val);
      }
    }
    if (!builder.isEmpty())
    {
      mods.add(new Modification(modType, builder.toAttribute()));
    }
  }
  private static void addSchemaDefinition(Set<String> definitions, String line, String attrName, String fileName)
      throws ParseException
  {
    definitions.add(getSchemaDefinition(line.substring(attrName.length()), fileName));
  }
  private static File getConcatenatedSchemaFile() throws InitializationException
  {
    File configDirectory = new File(DirectoryServer.getConfigFile()).getParentFile();
    File upgradeDirectory = new File(configDirectory, "upgrade");
    File concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
    if (concatFile.exists())
    {
      return concatFile.getAbsoluteFile();
    }
    String fileName = SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION + BuildVersion.instanceVersion().getRevision();
    concatFile = new File(upgradeDirectory, fileName);
    if (concatFile.exists())
    {
      return concatFile.getAbsoluteFile();
    }
    String runningUnitTestsStr = System.getProperty(PROPERTY_RUNNING_UNIT_TESTS);
    if ("true".equalsIgnoreCase(runningUnitTestsStr))
    {
      writeConcatenatedSchema();
      concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
      return concatFile.getAbsoluteFile();
    }
    throw new InitializationException(ERR_SCHEMA_CANNOT_FIND_CONCAT_FILE.get(upgradeDirectory.getAbsolutePath(),
        SCHEMA_CONCAT_FILE_NAME, concatFile.getName()));
  }
  private static String getSchemaDefinition(String definition, String schemaFile) throws ParseException
  {
    if (definition.startsWith("::"))
    {
      // See OPENDJ-2792: the definition of the ds-cfg-csv-delimiter-char attribute type
      // had a space accidentally added after the closing parenthesis.
      // This was unfortunately interpreted as base64
      definition = ByteString.wrap(Base64.decode(definition.substring(2).trim())).toString();
    }
    else if (definition.startsWith(":"))
    {
      definition = definition.substring(1).trim();
    }
    else
    {
      throw new ParseException(ERR_SCHEMA_COULD_NOT_PARSE_DEFINITION.get().toString(), 0);
    }
    return SchemaUtils.addSchemaFileToElementDefinitionIfAbsent(definition, schemaFile);
  }
  private static void parseSchemaLine(String definition, String fileName, Set<String> attributeTypes,
      Set<String> objectClasses, Set<String> nameForms, Set<String> ditContentRules, Set<String> ditStructureRules,
      Set<String> matchingRuleUses, Set<String> ldapSyntaxes)
  {
    String lowerLine = toLowerCase(definition);
    try
    {
      if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
      {
        addSchemaDefinition(attributeTypes, definition, ATTR_ATTRIBUTE_TYPES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
      {
        addSchemaDefinition(objectClasses, definition, ATTR_OBJECTCLASSES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
      {
        addSchemaDefinition(nameForms, definition, ATTR_NAME_FORMS_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
      {
        addSchemaDefinition(ditContentRules, definition, ATTR_DIT_CONTENT_RULES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
      {
        addSchemaDefinition(ditStructureRules, definition, ATTR_DIT_STRUCTURE_RULES_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
      {
        addSchemaDefinition(matchingRuleUses, definition, ATTR_MATCHING_RULE_USE_LC, fileName);
      }
      else if (lowerLine.startsWith(ATTR_LDAP_SYNTAXES_LC))
      {
        addSchemaDefinition(ldapSyntaxes, definition, ATTR_LDAP_SYNTAXES_LC, fileName);
      }
    }
    catch (ParseException pe)
    {
      logger.error(ERR_SCHEMA_PARSE_LINE.get(definition, pe.getLocalizedMessage()));
    }
  }
  private static List<StringBuilder> readSchemaElementsFromLdif(File f) throws IOException, FileNotFoundException
  {
    final LinkedList<StringBuilder> lines = new LinkedList<>();
    try (BufferedReader reader = new BufferedReader(new FileReader(f)))
    {
      String line;
      while ((line = reader.readLine()) != null)
      {
        if (line.startsWith("#") || line.length() == 0)
        {
          continue;
        }
        else if (line.startsWith(" "))
        {
          lines.getLast().append(line.substring(1));
        }
        else
        {
          lines.add(new StringBuilder(line));
        }
      }
    }
    return lines;
  }
  private static void writeLines(BufferedWriter writer, String... lines) throws IOException
  {
    for (String line : lines)
    {
      writer.write(line);
      writer.newLine();
    }
  }
  private static void writeLines(BufferedWriter writer, String beforeColumn, Set<String> lines) throws IOException
  {
    for (String line : lines)
    {
      writer.write(beforeColumn);
      writer.write(": ");
      writer.write(line);
      writer.newLine();
    }
  }
}
opendj-server-legacy/src/main/java/org/opends/server/util/SchemaUtils.java
@@ -15,7 +15,9 @@
 */
package org.opends.server.util;
import static org.opends.server.types.Schema.addSchemaFileToElementDefinitionIfAbsent;
import static org.opends.messages.SchemaMessages.*;
import static org.opends.server.util.ServerConstants.SCHEMA_PROPERTY_FILENAME;
import static org.opends.server.schema.SchemaConstants.SYNTAX_AUTH_PASSWORD_OID;
import static org.opends.server.schema.SchemaConstants.SYNTAX_USER_PASSWORD_OID;
@@ -24,12 +26,18 @@
import java.util.List;
import java.util.Set;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.schema.AttributeType;
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.opends.server.core.ServerContext;
import org.opends.server.types.DirectoryException;
import com.forgerock.opendj.util.SubstringReader;
/** Utility methods related to schema. */
public class SchemaUtils
@@ -189,4 +197,244 @@
    }
    return attributeType;
  }
  /**
   * Adds the provided schema file to the provided schema element definition.
   *
   * @param definition
   *            The schema element definition
   * @param schemaFile
   *            The name of the schema file to include in the definition
   * @return  The definition string of the element
   *          including the X-SCHEMA-FILE extension.
   */
  public static String addSchemaFileToElementDefinitionIfAbsent(String definition, String schemaFile)
  {
    if (schemaFile != null && !definition.contains(SCHEMA_PROPERTY_FILENAME))
    {
      int pos = definition.lastIndexOf(')');
      return definition.substring(0, pos).trim() + " " + SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
    }
    return definition;
  }
  /**
   * Parses the schema file (value of X-SCHEMA-FILE extension) from the provided schema element
   * definition.
   * <p>
   * It expects a single value for the X-SCHEMA-FILE extension, e.g.:
   * "X-SCHEMA-FILE '99-user.ldif'", as there is no sensible meaning for multiple values.
   *
   * @param definition
   *          The definition of a schema element
   * @return the value of the schema file or {@code null} if the X-SCHEMA-FILE extension is not
   *         present in the definition
   * @throws DirectoryException
   *            If an error occurs while parsing the schema element definition
   */
  public static String parseSchemaFileFromElementDefinition(String definition) throws DirectoryException
  {
    int pos = definition.lastIndexOf(SCHEMA_PROPERTY_FILENAME);
    if (pos == -1)
    {
      return null;
    }
    SubstringReader reader = new SubstringReader(definition);
    reader.read(pos + SCHEMA_PROPERTY_FILENAME.length());
    int length = 0;
    reader.skipWhitespaces();
    reader.mark();
    try
    {
      // Accept both a quoted value or an unquoted value
      char c = reader.read();
      if (c == '\'')
      {
        reader.mark();
        // Parse until the closing quote.
        while (reader.read() != '\'')
        {
          length++;
        }
      }
      else
      {
        // Parse until the next space.
        do
        {
          length++;
        }
        while (reader.read() != ' ');
      }
      reader.reset();
      return reader.read(length);
    }
    catch (final StringIndexOutOfBoundsException e)
    {
      // TODO : write the correct message = Error when trying to parse the schema file from a schema
      // element definition
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, LocalizableMessage.raw(""));
    }
  }
  /**
   * Returns the OID from the provided attribute type definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of an attribute type, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseAttributeTypeOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_ATTRIBUTE_TYPE_OID);
  }
  /**
   * Returns the OID from the provided object class definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a object class, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseObjectClassOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_OBJECTCLASS_OID);
  }
  /**
   * Returns the OID from the provided name form definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a name form, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseNameFormOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_NAME_FORM_OID);
  }
  /**
   * Returns the OID from the provided DIT content rule definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a DIT content rule, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseDITContentRuleOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_DIT_CONTENT_RULE_OID);
  }
  /**
   * Returns the ruleID from the provided DIT structure rule definition, assuming the definition is
   * valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a DIT structure rule, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static int parseRuleID(String definition) throws DirectoryException
  {
    // Reuse code of parseOID, even though this is not an OID
    return Integer.parseInt(parseOID(definition, ERR_PARSING_DIT_STRUCTURE_RULE_RULEID));
  }
  /**
   * Returns the OID from the provided matching rule use definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a matching rule use, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseMatchingRuleUseOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_MATCHING_RULE_USE_OID);
  }
  /**
   * Returns the OID from the provided syntax definition, assuming the definition is valid.
   * <p>
   * This method does not perform any check.
   *
   * @param definition
   *          The definition of a syntax, assumed to be valid
   * @return the OID, which is never {@code null}
   * @throws DirectoryException
   *           If a problem occurs while parsing the definition
   */
  public static String parseSyntaxOID(String definition) throws DirectoryException
  {
    return parseOID(definition, ERR_PARSING_LDAP_SYNTAX_OID);
  }
  /**
   * Returns the OID from the provided definition, using the provided message if an error occurs.
   *
   * @param definition
   *            The definition of a schema element
   * @param parsingErrorMsg
   *            Error message to use in case of failure (should be related to
   *            the specific schema element parsed)
   * @return the OID corresponding to the definition
   * @throws DirectoryException
   *            If the parsing of the definition fails
   *
   * */
  public static String parseOID(String definition, Arg1<Object> parsingErrorMsg) throws DirectoryException
  {
    try
    {
      int pos = 0;
      int length = definition.length();
      // Skip over any leading whitespace.
      while (pos < length && (definition.charAt(pos) == ' '))
      {
        pos++;
      }
      // Skip the open parenthesis.
      pos++;
      // Skip over any spaces immediately following the opening parenthesis.
      while (pos < length && definition.charAt(pos) == ' ')
      {
        pos++;
      }
      // The next set of characters must be the OID.
      int oidStartPos = pos;
      while (pos < length && definition.charAt(pos) != ' ' && definition.charAt(pos) != ')')
      {
        pos++;
      }
      return definition.substring(oidStartPos, pos);
    }
    catch (IndexOutOfBoundsException e)
    {
      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, parsingErrorMsg.get(definition), e);
    }
  }
}