/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import org.opends.messages.Message;



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

import org.opends.server.config.ConfigException;
import org.opends.server.schema.AttributeTypeSyntax;
import org.opends.server.schema.DITContentRuleSyntax;
import org.opends.server.schema.DITStructureRuleSyntax;
import org.opends.server.schema.MatchingRuleUseSyntax;
import org.opends.server.schema.NameFormSyntax;
import org.opends.server.schema.ObjectClassSyntax;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DITContentRule;
import org.opends.server.types.DITStructureRule;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.MatchingRuleUse;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.NameForm;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.Schema;
import org.opends.server.util.LDIFReader;

import static org.opends.server.config.ConfigConstants.*;
import org.opends.server.types.DebugLogLevel;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.loggers.ErrorLogger.*;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.schema.LDAPSyntaxDescriptionSyntax;
import org.opends.server.types.LDAPSyntaxDescription;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.server.schema.SchemaConstants.*;
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
{
  /**
   * The tracer object for the debug logger.
   */
  private static final DebugTracer TRACER = getTracer();

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



  /**
   * Creates a new instance of this schema config manager.
   */
  public SchemaConfigManager()
  {
    schema = new Schema();
  }



  /**
   * Retrieves the path to the directory containing the server schema files.
   *
   * @param userSchema indicates if we need to retrieve user schema or
   * "unmodified" schema.
   *
   * @return  The path to the directory containing the server schema files.
   */
  public static String getSchemaDirectoryPath(boolean userSchema)
  {
    File schemaDir =
              DirectoryServer.getEnvironmentConfig().
                getSchemaDirectory(userSchema);
    if (schemaDir != null) {
      return schemaDir.getAbsolutePath();
    } else {
      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();
    syntaxConfigManager.initializeAttributeSyntaxes();
  }



  /**
   * 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 schemaInstallDirPath   = getSchemaDirectoryPath(false);
    String schemaInstanceDirPath  = getSchemaDirectoryPath(true);
    File schemaInstallDir         = new File(schemaInstallDirPath);
    File schemaInstanceDir        = null;

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

    try
    {
      if (schemaInstallDir == null || ! schemaInstallDir.exists())
      {
        Message message =
          ERR_CONFIG_SCHEMA_NO_SCHEMA_DIR.get(schemaInstallDirPath);
        throw new InitializationException(message);
      }
      if (! schemaInstallDir.isDirectory())
      {
        Message message =
            ERR_CONFIG_SCHEMA_DIR_NOT_DIRECTORY.get(schemaInstallDirPath);
        throw new InitializationException(message);
      }

      if ((schemaInstanceDir == null) || (!schemaInstanceDir.exists())
          || (! schemaInstanceDir.isDirectory()))
      {
        schemaInstanceDir = null;
      }

      File[] schemaInstallDirFiles  = schemaInstallDir.listFiles() ;
      int fileNumber = schemaInstallDirFiles.length;
      File[] schemaInstanceDirFiles = null ;
      if (schemaInstanceDir != null)
      {
        schemaInstanceDirFiles = schemaInstanceDir.listFiles();
        fileNumber =+ schemaInstanceDirFiles.length ;
      }

      ArrayList<String> fileList = new ArrayList<String>(fileNumber);
      for (File f : schemaInstallDirFiles)
      {
        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;
        }
      }
      if (schemaInstanceDirFiles != null)
      {
        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)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, ie);
      }

      throw ie;
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message = ERR_CONFIG_SCHEMA_CANNOT_LIST_FILES.get(
          schemaInstallDirPath, 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(schema, schemaFile, false);
    }
  }



  /**
   * 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.
   *
   * @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(Schema schema,
                                                  String schemaFile)
         throws ConfigException, InitializationException
  {
    return loadSchemaFile(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(Schema schema,
                                                   String schemaFile,
                                                   boolean failOnError)
         throws ConfigException, InitializationException
  {
    // Create an LDIF reader to use when reading the files.
    String schemaDirPath = null;

    schemaDirPath = getSchemaDirectoryPath(true);
    File f = new File(schemaDirPath, schemaFile);
    if (!f.exists())
    {
      schemaDirPath = getSchemaDirectoryPath(false);
      f = new File(schemaDirPath, schemaFile);
    }
    LDIFReader reader;
    try
    {
      reader = new LDIFReader(new LDIFImportConfig(f.getAbsolutePath()));
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

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

      if (failOnError)
      {
        throw new ConfigException(message);
      }
      else
      {
        logError(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<Modification>();
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

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

      if (failOnError)
      {
        throw new InitializationException(message, e);
      }
      else
      {
        logError(message);
        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)
      {
        Message message = WARN_CONFIG_SCHEMA_MULTIPLE_ENTRIES_IN_FILE.get(
            schemaFile, schemaDirPath);
        logError(message);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      Message message = WARN_CONFIG_SCHEMA_UNPARSEABLE_EXTRA_DATA_IN_FILE.get(
          schemaFile, schemaDirPath, getExceptionMessage(e));
      logError(message);
    }

    try
    {
      reader.close();
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }
    }


    // Get the attributeTypes attribute from the entry.
    LinkedList<Modification> mods = new LinkedList<Modification>();
    //parse the syntaxes first because attributes rely on these.
    LDAPSyntaxDescriptionSyntax ldapSyntax;
    try
    {
      ldapSyntax = (LDAPSyntaxDescriptionSyntax) schema.getSyntax(
              SYNTAX_LDAP_SYNTAX_OID);
      if (ldapSyntax == null)
      {
        ldapSyntax = new LDAPSyntaxDescriptionSyntax();
        ldapSyntax.initializeSyntax(null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      ldapSyntax = new LDAPSyntaxDescriptionSyntax();
      ldapSyntax.initializeSyntax(null);
    }

    AttributeType ldapSyntaxAttrType =
         schema.getAttributeType(ATTR_LDAP_SYNTAXES_LC);
    if (ldapSyntaxAttrType == null)
    {
      ldapSyntaxAttrType =
           DirectoryServer.getDefaultAttributeType(ATTR_LDAP_SYNTAXES,
                                                   ldapSyntax);
    }

    List<Attribute> ldapSyntaxList = entry.getAttribute(ldapSyntaxAttrType);
    if ((ldapSyntaxList != null) && (! ldapSyntaxList.isEmpty()))
    {
      for (Attribute a : ldapSyntaxList)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }

    AttributeTypeSyntax attrTypeSyntax;
    try
    {
      attrTypeSyntax = (AttributeTypeSyntax)
                       schema.getSyntax(SYNTAX_ATTRIBUTE_TYPE_OID);
      if (attrTypeSyntax == null)
      {
        attrTypeSyntax = new AttributeTypeSyntax();
        attrTypeSyntax.initializeSyntax(null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      attrTypeSyntax = new AttributeTypeSyntax();
      attrTypeSyntax.initializeSyntax(null);
    }

    AttributeType attributeAttrType =
         schema.getAttributeType(ATTR_ATTRIBUTE_TYPES_LC);
    if (attributeAttrType == null)
    {
      attributeAttrType =
           DirectoryServer.getDefaultAttributeType(ATTR_ATTRIBUTE_TYPES,
                                                   attrTypeSyntax);
    }

    List<Attribute> attrList = entry.getAttribute(attributeAttrType);
    if ((attrList != null) && (! attrList.isEmpty()))
    {
      for (Attribute a : attrList)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }


    // Get the objectClasses attribute from the entry.
    ObjectClassSyntax ocSyntax;
    try
    {
      ocSyntax = (ObjectClassSyntax) schema.getSyntax(SYNTAX_OBJECTCLASS_OID);
      if (ocSyntax == null)
      {
        ocSyntax = new ObjectClassSyntax();
        ocSyntax.initializeSyntax(null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      ocSyntax = new ObjectClassSyntax();
      ocSyntax.initializeSyntax(null);
    }

    AttributeType objectclassAttrType =
         schema.getAttributeType(ATTR_OBJECTCLASSES_LC);
    if (objectclassAttrType == null)
    {
      objectclassAttrType =
           DirectoryServer.getDefaultAttributeType(ATTR_OBJECTCLASSES,
                                                   ocSyntax);
    }

    List<Attribute> ocList = entry.getAttribute(objectclassAttrType);
    if ((ocList != null) && (! ocList.isEmpty()))
    {
      for (Attribute a : ocList)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }


    // Get the name forms attribute from the entry.
    NameFormSyntax nfSyntax;
    try
    {
      nfSyntax = (NameFormSyntax) schema.getSyntax(SYNTAX_NAME_FORM_OID);
      if (nfSyntax == null)
      {
        nfSyntax = new NameFormSyntax();
        nfSyntax.initializeSyntax(null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      nfSyntax = new NameFormSyntax();
      nfSyntax.initializeSyntax(null);
    }

    AttributeType nameFormAttrType =
         schema.getAttributeType(ATTR_NAME_FORMS_LC);
    if (nameFormAttrType == null)
    {
      nameFormAttrType =
           DirectoryServer.getDefaultAttributeType(ATTR_NAME_FORMS, nfSyntax);
    }

    List<Attribute> nfList = entry.getAttribute(nameFormAttrType);
    if ((nfList != null) && (! nfList.isEmpty()))
    {
      for (Attribute a : nfList)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }


    // Get the DIT content rules attribute from the entry.
    DITContentRuleSyntax dcrSyntax;
    try
    {
      dcrSyntax = (DITContentRuleSyntax)
                  schema.getSyntax(SYNTAX_DIT_CONTENT_RULE_OID);
      if (dcrSyntax == null)
      {
        dcrSyntax = new DITContentRuleSyntax();
        dcrSyntax.initializeSyntax(null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      dcrSyntax = new DITContentRuleSyntax();
      dcrSyntax.initializeSyntax(null);
    }

    AttributeType dcrAttrType =
         schema.getAttributeType(ATTR_DIT_CONTENT_RULES_LC);
    if (dcrAttrType == null)
    {
      dcrAttrType =
           DirectoryServer.getDefaultAttributeType(ATTR_DIT_CONTENT_RULES,
                                                   dcrSyntax);
    }

    List<Attribute> dcrList = entry.getAttribute(dcrAttrType);
    if ((dcrList != null) && (! dcrList.isEmpty()))
    {
      for (Attribute a : dcrList)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }


    // Get the DIT structure rules attribute from the entry.
    DITStructureRuleSyntax dsrSyntax;
    try
    {
      dsrSyntax = (DITStructureRuleSyntax)
                  schema.getSyntax(SYNTAX_DIT_STRUCTURE_RULE_OID);
      if (dsrSyntax == null)
      {
        dsrSyntax = new DITStructureRuleSyntax();
        dsrSyntax.initializeSyntax(null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      dsrSyntax = new DITStructureRuleSyntax();
      dsrSyntax.initializeSyntax(null);
    }

    AttributeType dsrAttrType =
         schema.getAttributeType(ATTR_DIT_STRUCTURE_RULES_LC);
    if (dsrAttrType == null)
    {
      dsrAttrType =
           DirectoryServer.getDefaultAttributeType(ATTR_DIT_STRUCTURE_RULES,
                                                   dsrSyntax);
    }

    List<Attribute> dsrList = entry.getAttribute(dsrAttrType);
    if ((dsrList != null) && (! dsrList.isEmpty()))
    {
      for (Attribute a : dsrList)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }


    // Get the matching rule uses attribute from the entry.
    MatchingRuleUseSyntax mruSyntax;
    try
    {
      mruSyntax = (MatchingRuleUseSyntax)
                  schema.getSyntax(SYNTAX_MATCHING_RULE_USE_OID);
      if (mruSyntax == null)
      {
        mruSyntax = new MatchingRuleUseSyntax();
        mruSyntax.initializeSyntax(null);
      }
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        TRACER.debugCaught(DebugLogLevel.ERROR, e);
      }

      mruSyntax = new MatchingRuleUseSyntax();
      mruSyntax.initializeSyntax(null);
    }

    AttributeType mruAttrType =
         schema.getAttributeType(ATTR_MATCHING_RULE_USE_LC);
    if (mruAttrType == null)
    {
      mruAttrType =
           DirectoryServer.getDefaultAttributeType(ATTR_MATCHING_RULE_USE,
                                                   mruSyntax);
    }

    List<Attribute> mruList = entry.getAttribute(mruAttrType);
    if ((mruList != null) && (! mruList.isEmpty()))
    {
      for (Attribute a : mruList)
      {
        mods.add(new Modification(ModificationType.ADD, a));
      }
    }

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



    // Parse the ldapsyntaxes definitions if there are any.
    if (ldapSyntaxList != null)
    {
      for (Attribute a : ldapSyntaxList)
      {
        for (AttributeValue v : a)
        {
          LDAPSyntaxDescription syntaxDescription = null;
          try
          {
            syntaxDescription = LDAPSyntaxDescriptionSyntax.decodeLDAPSyntax(
                    v.getValue(),schema,false);
            syntaxDescription.setExtraProperty(
                    SCHEMA_PROPERTY_FILENAME, (String) null);
            syntaxDescription.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_LDAP_SYNTAX.get(
                    schemaFile,
                    de.getMessageObject());

            if (failOnError)
            {
              throw new ConfigException(message, de);
            }
            else
            {
              logError(message);
              continue;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_LDAP_SYNTAX.get(
                    schemaFile,
                    v.getValue().toString() + ":  " + getExceptionMessage(e));

            if (failOnError)
            {
              throw new ConfigException(message, e);
            }
            else
            {
              logError(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);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CONFLICTING_LDAP_SYNTAX.get(
                schemaFile, de.getMessageObject());
            logError(message);

            try
            {
              schema.registerLdapSyntaxDescription(syntaxDescription, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }

        }
      }
    }

    // Parse the attribute type definitions if there are any.
    if (attrList != null)
    {
      for (Attribute a : attrList)
      {
        for (AttributeValue v : a)
        {
          // Parse the attribute type.
          AttributeType attrType;
          try
          {
            attrType = AttributeTypeSyntax.decodeAttributeType(v.getValue(),
                                                          schema, false);
            attrType.setExtraProperty(SCHEMA_PROPERTY_FILENAME, (String) null);
            attrType.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_ATTR_TYPE.get(
                    schemaFile, de.getMessageObject());

            if (failOnError)
            {
              throw new ConfigException(message, de);
            }
            else
            {
              logError(message);
              continue;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_ATTR_TYPE.get(
                    schemaFile, v.getValue().toString() + ":  " +
                    getExceptionMessage(e));
            if (failOnError)
            {
              throw new ConfigException(message, e);
            }
            else
            {
              logError(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)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CONFLICTING_ATTR_TYPE.get(
                schemaFile, de.getMessageObject());
            logError(message);

            try
            {
              schema.registerAttributeType(attrType, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
        }
      }
    }


    // Parse the objectclass definitions if there are any.
    if (ocList != null)
    {
      for (Attribute a : ocList)
      {
        for (AttributeValue v : a)
        {
          // Parse the objectclass.
          ObjectClass oc;
          try
          {
            oc =
              ObjectClassSyntax.decodeObjectClass(v.getValue(), schema, false);
            oc.setExtraProperty(SCHEMA_PROPERTY_FILENAME, (String) null);
            oc.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_OC.get(
                    schemaFile,
                    de.getMessageObject());

            if (failOnError)
            {
              throw new ConfigException(message, de);
            }
            else
            {
              logError(message);
              continue;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_OC.get(
                    schemaFile,
                    v.getValue().toString() + ":  " + getExceptionMessage(e));

            if (failOnError)
            {
              throw new ConfigException(message, e);
            }
            else
            {
              logError(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)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CONFLICTING_OC.get(
                schemaFile, de.getMessageObject());
            logError(message);

            try
            {
              schema.registerObjectClass(oc, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
        }
      }
    }


    // Parse the name form definitions if there are any.
    if (nfList != null)
    {
      for (Attribute a : nfList)
      {
        for (AttributeValue v : a)
        {
          // Parse the name form.
          NameForm nf;
          try
          {
            nf = NameFormSyntax.decodeNameForm(v.getValue(), schema, false);
            nf.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            nf.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_NAME_FORM.get(
                    schemaFile, de.getMessageObject());
            if (failOnError)
            {
              throw new ConfigException(message, de);
            }
            else
            {
              logError(message);
              continue;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_NAME_FORM.get(
                    schemaFile,  v.getValue().toString() + ":  " +
                    getExceptionMessage(e));

            if (failOnError)
            {
              throw new ConfigException(message, e);
            }
            else
            {
              logError(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)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CONFLICTING_NAME_FORM.get(
                schemaFile, de.getMessageObject());
            logError(message);

            try
            {
              schema.registerNameForm(nf, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
        }
      }
    }


    // Parse the DIT content rule definitions if there are any.
    if (dcrList != null)
    {
      for (Attribute a : dcrList)
      {
        for (AttributeValue v : a)
        {
          // Parse the DIT content rule.
          DITContentRule dcr;
          try
          {
            dcr = DITContentRuleSyntax.decodeDITContentRule(
                v.getValue(), schema, false);
            dcr.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            dcr.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DCR.get(
                    schemaFile, de.getMessageObject());

            if (failOnError)
            {
              throw new ConfigException(message, de);
            }
            else
            {
              logError(message);
              continue;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DCR.get(
                    schemaFile,v.getValue().toString() + ":  " +
                    getExceptionMessage(e));

            if (failOnError)
            {
              throw new ConfigException(message, e);
            }
            else
            {
              logError(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)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CONFLICTING_DCR.get(
                schemaFile, de.getMessageObject());
            logError(message);

            try
            {
              schema.registerDITContentRule(dcr, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
        }
      }
    }


    // Parse the DIT structure rule definitions if there are any.
    if (dsrList != null)
    {
      for (Attribute a : dsrList)
      {
        for (AttributeValue v : a)
        {
          // Parse the DIT content rule.
          DITStructureRule dsr;
          try
          {
            dsr = DITStructureRuleSyntax.decodeDITStructureRule(
                v.getValue(), schema, false);
            dsr.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            dsr.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DSR.get(
                    schemaFile, de.getMessageObject());

            if (failOnError)
            {
              throw new ConfigException(message, de);
            }
            else
            {
              logError(message);
              continue;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_DSR.get(
                    schemaFile, v.getValue().toString() + ":  " +
                                        getExceptionMessage(e));

            if (failOnError)
            {
              throw new ConfigException(message, e);
            }
            else
            {
              logError(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)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CONFLICTING_DSR.get(
                schemaFile, de.getMessageObject());
            logError(message);

            try
            {
              schema.registerDITStructureRule(dsr, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
        }
      }
    }


    // Parse the matching rule use definitions if there are any.
    if (mruList != null)
    {
      for (Attribute a : mruList)
      {
        for (AttributeValue v : a)
        {
          // Parse the matching rule use definition.
          MatchingRuleUse mru;
          try
          {
            mru = MatchingRuleUseSyntax.decodeMatchingRuleUse(
                            v.getValue(), schema, false);
            mru.getExtraProperties().remove(SCHEMA_PROPERTY_FILENAME);
            mru.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_MRU.get(
                    schemaFile, de.getMessageObject());

            if (failOnError)
            {
              throw new ConfigException(message, de);
            }
            else
            {
              logError(message);
              continue;
            }
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, e);
            }

            Message message = WARN_CONFIG_SCHEMA_CANNOT_PARSE_MRU.get(
                    schemaFile,
                    v.getValue().toString() + ":  " +
                    getExceptionMessage(e));

            if (failOnError)
            {
              throw new ConfigException(message, e);
            }
            else
            {
              logError(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)
          {
            if (debugEnabled())
            {
              TRACER.debugCaught(DebugLogLevel.ERROR, de);
            }

            Message message = WARN_CONFIG_SCHEMA_CONFLICTING_MRU.get(
                schemaFile, de.getMessageObject());
            logError(message);

            try
            {
              schema.registerMatchingRuleUse(mru, true);
            }
            catch (Exception e)
            {
              // This should never happen.
              if (debugEnabled())
              {
                TRACER.debugCaught(DebugLogLevel.ERROR, e);
              }
            }
          }
        }
      }
    }

    return mods;
  }



  /**
   * 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.
   */
  private static boolean isSchemaAttribute(Attribute attribute)
  {
    String attributeOid = attribute.getAttributeType().getOID();
    if (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("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")

        )
    {
      return true;
    }
    else
    {
      return false;
    }
  }
}

