/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2015 ForgeRock AS
 */
package org.opends.server.core;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ModificationType;
import org.forgerock.opendj.ldap.schema.CoreSchema;
import org.forgerock.opendj.ldap.schema.SchemaBuilder;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.opends.server.schema.*;
import org.opends.server.types.*;
import org.opends.server.util.LDIFReader;
import org.opends.server.util.StaticUtils;

import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.types.CommonSchemaElements.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;

/**
 * This class defines a utility that will be used to manage the interaction with
 * the Directory Server schema.  It will be used to initially load all of the
 * matching rules and attribute syntaxes that have been defined in the
 * configuration, and will then read the actual schema definitions.  At present,
 * only attribute types and objectclasses are supported in the schema config
 * files.  Other components like DIT content rules, DIT structure rules, name
 * forms, and matching rule use definitions will be ignored.
 */
public class SchemaConfigManager
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  /** The schema that has been parsed from the server configuration. */
  private Schema schema;

  private final ServerContext serverContext;

  /**
   * Creates a new instance of this schema config manager.
   *
   * @param serverContext
   *            The server context.
   */
  public SchemaConfigManager(ServerContext serverContext)
  {
    this.serverContext = serverContext;
    schema = new Schema();
  }



  /**
   * Retrieves the path to the directory containing the server schema files.
   *
   * @return  The path to the directory containing the server schema files.
   */
  public static String getSchemaDirectoryPath()
  {
    File schemaDir =
        DirectoryServer.getEnvironmentConfig().getSchemaDirectory();
    if (schemaDir != null) {
      return schemaDir.getAbsolutePath();
    }
    return null;
  }



  /**
   * Retrieves a reference to the schema information that has been read from the
   * server configuration.  Note that this information will not be complete
   * until the <CODE>initializeMatchingRules</CODE>,
   * <CODE>initializeAttributeSyntaxes</CODE>, and
   * <CODE>initializeAttributeTypesAndObjectClasses</CODE> methods have been
   * called.
   *
   * @return  A reference to the schema information that has been read from the
   *          server configuration.
   */
  public Schema getSchema()
  {
    return schema;
  }



  /**
   * Initializes all the matching rules defined in the Directory Server
   * configuration.  This should only be called at Directory Server startup.
   *
   * @throws  ConfigException  If a configuration problem causes the matching
   *                           rule initialization process to fail.
   *
   * @throws  InitializationException  If a problem occurs while initializing
   *                                   the matching rules that is not related to
   *                                   the server configuration.
   */
  public void initializeMatchingRules()
         throws ConfigException, InitializationException
  {
    MatchingRuleConfigManager matchingRuleConfigManager =
         new MatchingRuleConfigManager();
    matchingRuleConfigManager.initializeMatchingRules();
  }



  /**
   * Initializes all the attribute syntaxes defined in the Directory Server
   * configuration.  This should only be called at Directory Server startup.
   *
   * @throws  ConfigException  If a configuration problem causes the syntax
   *                           initialization process to fail.
   *
   * @throws  InitializationException  If a problem occurs while initializing
   *                                   the syntaxes that is not related to the
   *                                   server configuration.
   */
  public void initializeAttributeSyntaxes()
         throws ConfigException, InitializationException
  {
    AttributeSyntaxConfigManager syntaxConfigManager =
         new AttributeSyntaxConfigManager(serverContext);
    syntaxConfigManager.initializeAttributeSyntaxes();
  }



  /**
   * Filter implementation that accepts only ldif files.
   */
  public static class SchemaFileFilter implements FilenameFilter
  {
    /** {@inheritDoc} */
    @Override
    public boolean accept(File directory, String filename)
    {
      return filename.endsWith(".ldif");
    }
  }



  /**
   * Initializes all the attribute type, object class, name form, DIT content
   * rule, DIT structure rule, and matching rule use definitions by reading the
   * server schema files.  These files will be located in a single directory and
   * will be processed in lexicographic order.  However, to make the order
   * easier to understand, they may be prefixed with a two digit number (with a
   * leading zero if necessary) so that they will be read in numeric order.
   * This should only be called at Directory Server startup.
   *
   * @throws  ConfigException  If a configuration problem causes the schema
   *                           element initialization to fail.
   *
   * @throws  InitializationException  If a problem occurs while initializing
   *                                   the schema elements that is not related
   *                                   to the server configuration.
   */
  public void initializeSchemaFromFiles()
         throws ConfigException, InitializationException
  {
    // Construct the path to the directory that should contain the schema files
    // and make sure that it exists and is a directory.  Get a list of the files
    // in that directory sorted in alphabetic order.
    String schemaInstanceDirPath  = getSchemaDirectoryPath();
    File schemaInstanceDir        = null;

    try
    {
      if (schemaInstanceDirPath != null)
      {
        schemaInstanceDir = new File(schemaInstanceDirPath);
      }
    } catch (Exception e)
    {
      schemaInstanceDir = null;
    }
    long oldestModificationTime   = -1L;
    long youngestModificationTime = -1L;
    String[] fileNames;

    try
    {
      if (schemaInstanceDir == null || ! schemaInstanceDir.exists())
      {
        LocalizableMessage message =
          ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR.get(schemaInstanceDirPath);
        throw new InitializationException(message);
      }
      if (! schemaInstanceDir.isDirectory())
      {
        LocalizableMessage message =
            ERR_CONFIG_SCHEMA_DIR_NOT_DIRECTORY.get(schemaInstanceDirPath);
        throw new InitializationException(message);
      }


      FilenameFilter filter = new SchemaFileFilter();
      File[] schemaInstanceDirFiles =
                schemaInstanceDir.listFiles(filter);
      int fileNumber = schemaInstanceDirFiles.length ;
      ArrayList<String> fileList = new ArrayList<>(fileNumber);

      for (File f : schemaInstanceDirFiles)
      {
        if (f.isFile())
        {
          fileList.add(f.getName());
        }

        long modificationTime = f.lastModified();
        if (oldestModificationTime <= 0L ||
            modificationTime < oldestModificationTime)
        {
          oldestModificationTime = modificationTime;
        }

        if (youngestModificationTime <= 0 ||
            modificationTime > youngestModificationTime)
        {
          youngestModificationTime = modificationTime;
        }
      }

      fileNames = new String[fileList.size()];
      fileList.toArray(fileNames);
      Arrays.sort(fileNames);
    }
    catch (InitializationException ie)
    {
      logger.traceException(ie);

      throw ie;
    }
    catch (Exception e)
    {
      logger.traceException(e);

      LocalizableMessage message = ERR_CONFIG_SCHEMA_CANNOT_LIST_FILES.get(
          schemaInstanceDirPath, getExceptionMessage(e));
      throw new InitializationException(message, e);
    }


    // If the oldest and youngest modification timestamps didn't get set for
    // some reason, then set them to the current time.
    if (oldestModificationTime <= 0)
    {
      oldestModificationTime = System.currentTimeMillis();
    }

    if (youngestModificationTime <= 0)
    {
      youngestModificationTime = oldestModificationTime;
    }

    schema.setOldestModificationTime(oldestModificationTime);
    schema.setYoungestModificationTime(youngestModificationTime);


    // Iterate through the schema files and read them as an LDIF file containing
    // a single entry.  Then get the attributeTypes and objectClasses attributes
    // from that entry and parse them to initialize the server schema.
    for (String schemaFile : fileNames)
    {
      loadSchemaFile(serverContext, schema, schemaFile, false);
    }
  }



  /**
   * Loads the contents of the specified schema file into the provided schema.
   *
   * @param serverContext
   *          The server context.
   *
   * @param  schema      The schema in which the contents of the schema file are
   *                     to be loaded.
   * @param  schemaFile  The name of the schema file to be loaded into the
   *                     provided schema.
   *
   * @return  A list of the modifications that could be performed in order to
   *          obtain the contents of the file.
   *
   * @throws  ConfigException  If a configuration problem causes the schema
   *                           element initialization to fail.
   *
   * @throws  InitializationException  If a problem occurs while initializing
   *                                   the schema elements that is not related
   *                                   to the server configuration.
   */
  public static List<Modification> loadSchemaFile(ServerContext serverContext, Schema schema, String schemaFile)
         throws ConfigException, InitializationException
  {
    return loadSchemaFile(serverContext, schema, schemaFile, true);
  }



  /**
   * Loads the contents of the specified schema file into the provided schema.
   *
   * @param  schema       The schema in which the contents of the schema file
   *                      are to be loaded.
   * @param  schemaFile   The name of the schema file to be loaded into the
   *                      provided schema.
   * @param  failOnError  If {@code true}, indicates that this method should
   *                      throw an exception if certain kinds of errors occur.
   *                      If {@code false}, indicates that this method should
   *                      log an error message and return without an exception.
   *                      This should only be {@code false} when called from
   *                      {@code initializeSchemaFromFiles}.
   *
   * @return  A list of the modifications that could be performed in order to
   *          obtain the contents of the file, or {@code null} if a problem
   *          occurred and {@code failOnError} is {@code false}.
   *
   * @throws  ConfigException  If a configuration problem causes the schema
   *                           element initialization to fail.
   *
   * @throws  InitializationException  If a problem occurs while initializing
   *                                   the schema elements that is not related
   *                                   to the server configuration.
   */
  private static List<Modification> loadSchemaFile(ServerContext serverContext, Schema schema, String schemaFile,
      boolean failOnError) throws ConfigException, InitializationException
  {
    // Create an LDIF reader to use when reading the files.
    String schemaDirPath = getSchemaDirectoryPath();
    File f = new File(schemaDirPath, schemaFile);
    LDIFReader reader;
    try
    {
      reader = new LDIFReader(new LDIFImportConfig(f.getAbsolutePath()));
    }
    catch (Exception e)
    {
      logger.traceException(e);

      LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_OPEN_FILE.get(
              schemaFile, schemaDirPath, getExceptionMessage(e));

      if (failOnError)
      {
        throw new ConfigException(message);
      }
      else
      {
        logger.error(message);
        return null;
      }
    }


    // Read the LDIF entry from the file and close the file.
    Entry entry;
    try
    {
      entry = reader.readEntry(false);

      if (entry == null)
      {
        // The file was empty -- skip it.
        reader.close();
        return new LinkedList<>();
      }
    }
    catch (Exception e)
    {
      logger.traceException(e);

      LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_READ_LDIF_ENTRY.get(
              schemaFile, schemaDirPath, getExceptionMessage(e));

      if (failOnError)
      {
        throw new InitializationException(message, e);
      }
      else
      {
        logger.error(message);
        StaticUtils.close(reader);
        return null;
      }
    }

    // If there are any more entries in the file, then print a warning message.
    try
    {
      Entry e = reader.readEntry(false);
      if (e != null)
      {
        logger.warn(WARN_CONFIG_SCHEMA_MULTIPLE_ENTRIES_IN_FILE, schemaFile, schemaDirPath);
      }
    }
    catch (Exception e)
    {
      logger.traceException(e);

      logger.warn(WARN_CONFIG_SCHEMA_UNPARSEABLE_EXTRA_DATA_IN_FILE, schemaFile, schemaDirPath, getExceptionMessage(e));
    }
    finally
    {
      StaticUtils.close(reader);
    }

    // Get the attributeTypes attribute from the entry.
    List<Modification> mods = new LinkedList<>();

    //parse the syntaxes first because attributes rely on these.
    List<Attribute> ldapSyntaxList = getLdapSyntaxesAttributes(schema, entry, mods);
    List<Attribute> attrList = getAttributeTypeAttributes(schema, entry, mods);
    List<Attribute> ocList = getObjectClassesAttributes(schema, entry, mods);
    List<Attribute> nfList = getNameFormsAttributes(schema, entry, mods);
    List<Attribute> dcrList = getDITContentRulesAttributes(schema, entry, mods);
    List<Attribute> dsrList = getDITStructureRulesAttributes(schema, entry, mods);
    List<Attribute> mruList = getMatchingRuleUsesAttributes(schema, entry, mods);

    // Loop on all the attribute of the schema entry to
    // find the extra attribute that should be loaded in the Schema.
    for (Attribute attribute : entry.getAttributes())
    {
      if (!isSchemaAttribute(attribute))
      {
        schema.addExtraAttribute(attribute.getName(), attribute);
      }
    }

    parseLdapSyntaxesDefinitions(serverContext, schema, schemaFile, failOnError,
        ldapSyntaxList);
    parseAttributeTypeDefinitions(schema, schemaFile, failOnError, attrList);
    parseObjectclassDefinitions(schema, schemaFile, failOnError, ocList);
    parseNameFormDefinitions(schema, schemaFile, failOnError, nfList);
    parseDITContentRuleDefinitions(schema, schemaFile, failOnError, dcrList);
    parseDITStructureRuleDefinitions(schema, schemaFile, failOnError, dsrList);
    parseMatchingRuleUseDefinitions(schema, schemaFile, failOnError, mruList);

    return mods;
  }

  private static List<Attribute> getLdapSyntaxesAttributes(Schema schema,
      Entry entry, List<Modification> mods) throws ConfigException
  {
    Syntax syntax = schema.getSyntax(SYNTAX_LDAP_SYNTAX_OID);
    if (syntax == null)
    {
      syntax = CoreSchema.getLDAPSyntaxDescriptionSyntax();
    }

    AttributeType ldapSyntaxAttrType = getAttributeType(schema, ATTR_LDAP_SYNTAXES, ATTR_LDAP_SYNTAXES_LC, syntax);
    return createAddModifications(entry, mods, ldapSyntaxAttrType);
  }

  private static List<Attribute> getAttributeTypeAttributes(Schema schema,
      Entry entry, List<Modification> mods) throws ConfigException,
      InitializationException
  {
    Syntax syntax = schema.getSyntax(SYNTAX_ATTRIBUTE_TYPE_OID);
    if (syntax == null)
    {
      syntax = CoreSchema.getAttributeTypeDescriptionSyntax();
    }
    AttributeType attributeAttrType = getAttributeType(
        schema, ATTR_ATTRIBUTE_TYPES, ATTR_ATTRIBUTE_TYPES_LC, syntax);
    return createAddModifications(entry, mods, attributeAttrType);
  }

  /** Get the objectClasses attribute from the entry. */
  private static List<Attribute> getObjectClassesAttributes(Schema schema,
      Entry entry, List<Modification> mods) throws ConfigException,
      InitializationException
  {
    Syntax syntax = schema.getSyntax(SYNTAX_OBJECTCLASS_OID);
    if (syntax == null)
    {
      syntax = CoreSchema.getObjectClassDescriptionSyntax();
    }
    AttributeType objectclassAttrType = getAttributeType(schema, ATTR_OBJECTCLASSES, ATTR_OBJECTCLASSES_LC, syntax);
    return createAddModifications(entry, mods, objectclassAttrType);
  }

  /** Get the name forms attribute from the entry. */
  private static List<Attribute> getNameFormsAttributes(Schema schema,
      Entry entry, List<Modification> mods) throws ConfigException,
      InitializationException
  {
    Syntax syntax = schema.getSyntax(SYNTAX_NAME_FORM_OID);
    if (syntax == null)
    {
      syntax = CoreSchema.getNameFormDescriptionSyntax();
    }
    AttributeType nameFormAttrType = getAttributeType(
        schema, ATTR_NAME_FORMS, ATTR_NAME_FORMS_LC, syntax);
    return createAddModifications(entry, mods, nameFormAttrType);
  }

  /** Get the DIT content rules attribute from the entry. */
  private static List<Attribute> getDITContentRulesAttributes(Schema schema,
      Entry entry, List<Modification> mods) throws ConfigException,
      InitializationException
  {
    Syntax syntax = schema.getSyntax(SYNTAX_DIT_CONTENT_RULE_OID);
    if (syntax == null)
    {
      syntax = CoreSchema.getDITContentRuleDescriptionSyntax();
    }
    AttributeType dcrAttrType = getAttributeType(
        schema, ATTR_DIT_CONTENT_RULES, ATTR_DIT_CONTENT_RULES_LC, syntax);
    return createAddModifications(entry, mods, dcrAttrType);
  }

  /** Get the DIT structure rules attribute from the entry. */
  private static List<Attribute> getDITStructureRulesAttributes(Schema schema,
      Entry entry, List<Modification> mods) throws ConfigException,
      InitializationException
  {
    Syntax syntax = schema.getSyntax(SYNTAX_DIT_STRUCTURE_RULE_OID);
    if (syntax == null)
    {
      syntax = CoreSchema.getDITStructureRuleDescriptionSyntax();
    }
    AttributeType dsrAttrType = getAttributeType(schema, ATTR_DIT_STRUCTURE_RULES, ATTR_DIT_STRUCTURE_RULES_LC, syntax);
    return createAddModifications(entry, mods, dsrAttrType);
  }

  /** Get the matching rule uses attribute from the entry. */
  private static List<Attribute> getMatchingRuleUsesAttributes(Schema schema,
      Entry entry, List<Modification> mods) throws ConfigException,
      InitializationException
  {
    Syntax syntax = schema.getSyntax(SYNTAX_MATCHING_RULE_USE_OID);
    if (syntax == null)
    {
      syntax = CoreSchema.getMatchingRuleUseDescriptionSyntax();
    }
    AttributeType mruAttrType = getAttributeType(schema, ATTR_MATCHING_RULE_USE, ATTR_MATCHING_RULE_USE_LC, syntax);
    return createAddModifications(entry, mods, mruAttrType);
  }

  private static AttributeType getAttributeType(Schema schema, String attrName,
      String attrLowerName, Syntax syntax)
  {
    final AttributeType attrType = schema.getAttributeType(attrLowerName);
    if (attrType != null)
    {
      return attrType;
    }
    return DirectoryServer.getDefaultAttributeType(attrName, syntax);
  }

  private static List<Attribute> createAddModifications(Entry entry,
      List<Modification> mods, AttributeType attrType)
  {
    List<Attribute> attributes = entry.getAttribute(attrType);
    if (attributes != null && !attributes.isEmpty())
    {
      for (Attribute a : attributes)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }
    return attributes;
  }

  /** Parse the ldapsyntaxes definitions if there are any. */
  private static void parseLdapSyntaxesDefinitions(ServerContext serverContext, Schema schema,
      String schemaFile, boolean failOnError, List<Attribute> ldapSyntaxList)
      throws ConfigException
  {
    if (ldapSyntaxList != null)
    {
      for (Attribute a : ldapSyntaxList)
      {
        for (ByteString v : a)
        {
          LDAPSyntaxDescription syntaxDescription;
          try
          {
            syntaxDescription = LDAPSyntaxDescriptionSyntax.decodeLDAPSyntax(v, serverContext, schema, false, false);
            setExtraProperty(syntaxDescription, SCHEMA_PROPERTY_FILENAME, null);
            setSchemaFile(syntaxDescription, schemaFile);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_LDAP_SYNTAX.get(
                    schemaFile,
                    de.getMessageObject());
            reportError(failOnError, de, message);
            continue;
          }
          catch (Exception e)
          {
            logger.traceException(e);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_LDAP_SYNTAX.get(
                    schemaFile, v + ":  " + getExceptionMessage(e));
            reportError(failOnError, e, message);
            continue;
          }

           // Register it with the schema.  We will allow duplicates, with the
          // later definition overriding any earlier definition, but we want
          // to trap them and log a warning.
          try
          {
            schema.registerLdapSyntaxDescription(syntaxDescription, failOnError);
            registerLdapSyntaxInSchemaNG(serverContext, syntaxDescription, failOnError);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            logger.warn(WARN_CONFIG_SCHEMA_CONFLICTING_LDAP_SYNTAX, schemaFile, de.getMessageObject());

            try
            {
              schema.registerLdapSyntaxDescription(syntaxDescription, true);
              registerLdapSyntaxInSchemaNG(serverContext, syntaxDescription, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              logger.traceException(e);
            }
          }
        }
      }
    }
  }

  private static void registerLdapSyntaxInSchemaNG(ServerContext serverContext, LDAPSyntaxDescription syntaxDescription,
      boolean overwrite)
  {
     // The server context may be null when this code is reached through non-server code (e.g. gui tools)
     if (serverContext != null)
     {
        SchemaUpdater schemaUpdater = serverContext.getSchemaUpdater();
        Syntax.Builder builder = schemaUpdater.getSchemaBuilder().buildSyntax(syntaxDescription.getSyntax());
        SchemaBuilder schemaBuilder = overwrite ? builder.addToSchemaOverwrite() : builder.addToSchema();
        schemaUpdater.updateSchema(schemaBuilder.toSchema());
     }
  }

  /** Parse the attribute type definitions if there are any. */
  private static void parseAttributeTypeDefinitions(Schema schema,
      String schemaFile, boolean failOnError, List<Attribute> attrList)
      throws ConfigException
  {
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        for (ByteString v : a)
        {
          // Parse the attribute type.
          AttributeType attrType;
          try
          {
            attrType = AttributeTypeSyntax.decodeAttributeType(v, schema, false);
            setExtraProperty(attrType, SCHEMA_PROPERTY_FILENAME, null);
            setSchemaFile(attrType, schemaFile);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_ATTR_TYPE.get(
                    schemaFile, de.getMessageObject());
            reportError(failOnError, de, message);
            continue;
          }
          catch (Exception e)
          {
            logger.traceException(e);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_ATTR_TYPE.get(
                    schemaFile, v + ":  " + getExceptionMessage(e));
            reportError(failOnError, e, message);
            continue;
          }

          // Register it with the schema.  We will allow duplicates, with the
          // later definition overriding any earlier definition, but we want
          // to trap them and log a warning.
          try
          {
            schema.registerAttributeType(attrType, failOnError);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            logger.warn(WARN_CONFIG_SCHEMA_CONFLICTING_ATTR_TYPE, schemaFile, de.getMessageObject());

            try
            {
              schema.registerAttributeType(attrType, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              logger.traceException(e);
            }
          }
        }
      }
    }
  }

  /** Parse the objectclass definitions if there are any. */
  private static void parseObjectclassDefinitions(Schema schema,
      String schemaFile, boolean failOnError, List<Attribute> ocList)
      throws ConfigException
  {
    if (ocList != null)
    {
      for (Attribute a : ocList)
      {
        for (ByteString v : a)
        {
          // Parse the objectclass.
          ObjectClass oc;
          try
          {
            oc = ObjectClassSyntax.decodeObjectClass(v, schema, false);
            setExtraProperty(oc, SCHEMA_PROPERTY_FILENAME, null);
            setSchemaFile(oc, schemaFile);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_OC.get(
                    schemaFile,
                    de.getMessageObject());
            reportError(failOnError, de, message);
            continue;
          }
          catch (Exception e)
          {
            logger.traceException(e);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_OC.get(
                    schemaFile, v + ":  " + getExceptionMessage(e));
            reportError(failOnError, e, message);
            continue;
          }

          // Register it with the schema.  We will allow duplicates, with the
          // later definition overriding any earlier definition, but we want
          // to trap them and log a warning.
          try
          {
            schema.registerObjectClass(oc, failOnError);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            logger.warn(WARN_CONFIG_SCHEMA_CONFLICTING_OC, schemaFile, de.getMessageObject());

            try
            {
              schema.registerObjectClass(oc, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              logger.traceException(e);
            }
          }
        }
      }
    }
  }

  /** Parse the name form definitions if there are any. */
  private static void parseNameFormDefinitions(Schema schema,
      String schemaFile, boolean failOnError, List<Attribute> nfList)
      throws ConfigException
  {
    if (nfList != null)
    {
      for (Attribute a : nfList)
      {
        for (ByteString v : a)
        {
          // Parse the name form.
          NameForm nf;
          try
          {
            nf = NameFormSyntax.decodeNameForm(v, schema, false);
            nf.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            setSchemaFile(nf, schemaFile);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_NAME_FORM.get(
                    schemaFile, de.getMessageObject());
            reportError(failOnError, de, message);
            continue;
          }
          catch (Exception e)
          {
            logger.traceException(e);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_NAME_FORM.get(
                    schemaFile,  v + ":  " + getExceptionMessage(e));
            reportError(failOnError, e, message);
            continue;
          }

          // Register it with the schema.  We will allow duplicates, with the
          // later definition overriding any earlier definition, but we want
          // to trap them and log a warning.
          try
          {
            schema.registerNameForm(nf, failOnError);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            logger.warn(WARN_CONFIG_SCHEMA_CONFLICTING_NAME_FORM, schemaFile, de.getMessageObject());

            try
            {
              schema.registerNameForm(nf, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              logger.traceException(e);
            }
          }
        }
      }
    }
  }

  /** Parse the DIT content rule definitions if there are any. */
  private static void parseDITContentRuleDefinitions(Schema schema,
      String schemaFile, boolean failOnError, List<Attribute> dcrList)
      throws ConfigException
  {
    if (dcrList != null)
    {
      for (Attribute a : dcrList)
      {
        for (ByteString v : a)
        {
          // Parse the DIT content rule.
          DITContentRule dcr;
          try
          {
            dcr = DITContentRuleSyntax.decodeDITContentRule(v, schema, false);
            dcr.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            setSchemaFile(dcr, schemaFile);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DCR.get(
                    schemaFile, de.getMessageObject());
            reportError(failOnError, de, message);
            continue;
          }
          catch (Exception e)
          {
            logger.traceException(e);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DCR.get(
                    schemaFile, v + ":  " + getExceptionMessage(e));
            reportError(failOnError, e, message);
            continue;
          }

          // Register it with the schema.  We will allow duplicates, with the
          // later definition overriding any earlier definition, but we want
          // to trap them and log a warning.
          try
          {
            schema.registerDITContentRule(dcr, failOnError);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            logger.warn(WARN_CONFIG_SCHEMA_CONFLICTING_DCR, schemaFile, de.getMessageObject());

            try
            {
              schema.registerDITContentRule(dcr, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              logger.traceException(e);
            }
          }
        }
      }
    }
  }

  private static void reportError(boolean failOnError, Exception e,
      LocalizableMessage message) throws ConfigException
  {
    if (failOnError)
    {
      throw new ConfigException(message, e);
    }
    logger.error(message);
  }

  /** Parse the DIT structure rule definitions if there are any. */
  private static void parseDITStructureRuleDefinitions(Schema schema,
      String schemaFile, boolean failOnError, List<Attribute> dsrList)
      throws ConfigException
  {
    if (dsrList != null)
    {
      for (Attribute a : dsrList)
      {
        for (ByteString v : a)
        {
          // Parse the DIT content rule.
          DITStructureRule dsr;
          try
          {
            dsr = DITStructureRuleSyntax.decodeDITStructureRule(v, schema, false);
            dsr.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            setSchemaFile(dsr, schemaFile);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DSR.get(
                    schemaFile, de.getMessageObject());
            reportError(failOnError, de, message);
            continue;
          }
          catch (Exception e)
          {
            logger.traceException(e);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DSR.get(
                    schemaFile, v + ":  " + getExceptionMessage(e));
            reportError(failOnError, e, message);
            continue;
          }

          // Register it with the schema.  We will allow duplicates, with the
          // later definition overriding any earlier definition, but we want
          // to trap them and log a warning.
          try
          {
            schema.registerDITStructureRule(dsr, failOnError);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            logger.warn(WARN_CONFIG_SCHEMA_CONFLICTING_DSR, schemaFile, de.getMessageObject());

            try
            {
              schema.registerDITStructureRule(dsr, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              logger.traceException(e);
            }
          }
        }
      }
    }
  }

  /** Parse the matching rule use definitions if there are any. */
  private static void parseMatchingRuleUseDefinitions(Schema schema,
      String schemaFile, boolean failOnError, List<Attribute> mruList)
      throws ConfigException
  {
    if (mruList != null)
    {
      for (Attribute a : mruList)
      {
        for (ByteString v : a)
        {
          // Parse the matching rule use definition.
          MatchingRuleUse mru;
          try
          {
            mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(v, schema, false);
            mru.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            setSchemaFile(mru, schemaFile);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_MRU.get(
                    schemaFile, de.getMessageObject());
            reportError(failOnError, de, message);
            continue;
          }
          catch (Exception e)
          {
            logger.traceException(e);

            LocalizableMessage message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_MRU.get(
                    schemaFile, v + ":  " + getExceptionMessage(e));
            reportError(failOnError, e, message);
            continue;
          }

          // Register it with the schema.  We will allow duplicates, with the
          // later definition overriding any earlier definition, but we want
          // to trap them and log a warning.
          try
          {
            schema.registerMatchingRuleUse(mru, failOnError);
          }
          catch (DirectoryException de)
          {
            logger.traceException(de);

            logger.warn(WARN_CONFIG_SCHEMA_CONFLICTING_MRU, schemaFile, de.getMessageObject());

            try
            {
              schema.registerMatchingRuleUse(mru, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              logger.traceException(e);
            }
          }
        }
      }
    }
  }



  /**
   * This method checks if a given attribute is an attribute that
   * is used by the definition of the schema.
   *
   * @param attribute   The attribute to be checked.
   * @return            true if the attribute is part of the schema definition,
   *                    false if the attribute is not part of the schema
   *                    definition.
   */
  public static boolean isSchemaAttribute(Attribute attribute)
  {
    String attributeOid = attribute.getAttributeType().getOID();
    return attributeOid.equals("2.5.21.1") ||
        attributeOid.equals("2.5.21.2") ||
        attributeOid.equals("2.5.21.4") ||
        attributeOid.equals("2.5.21.5") ||
        attributeOid.equals("2.5.21.6") ||
        attributeOid.equals("2.5.21.7") ||
        attributeOid.equals("2.5.21.8") ||
        attributeOid.equals("2.5.4.3") ||
        attributeOid.equals("1.3.6.1.4.1.1466.101.120.16") ||
        attributeOid.equals("cn-oid") ||
        attributeOid.equals("attributetypes-oid") ||
        attributeOid.equals("objectclasses-oid") ||
        attributeOid.equals("matchingrules-oid") ||
        attributeOid.equals("matchingruleuse-oid") ||
        attributeOid.equals("nameformdescription-oid") ||
        attributeOid.equals("ditcontentrules-oid") ||
        attributeOid.equals("ditstructurerules-oid") ||
        attributeOid.equals("ldapsyntaxes-oid");
  }
}

