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

neil_a_wilson
12.36.2007 679051a2a5aea0a95298083f1840735ea40bc71c
Provide a mechanism for the server to notify the synchronization service about
any schema changes that occur. The synchronization service can already handle
schema changes that occur over protocol with the server online, but this set of
changes adds support for detecting schema changes made by directly editing the
schema configuration files with the server offline, as well as new schema
elements added through the add schema file task.

Reviewed By: Gilles Bellaton
OpenDS Issue Number: 1315
18 files modified
1148 ■■■■■ changed files
opends/build.xml 1 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/SynchronizationProvider.java 22 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java 135 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 43 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/SchemaConfigManager.java 79 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/BackendMessages.java 51 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/TaskMessages.java 16 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/synchronization/plugin/MultimasterSynchronization.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java 97 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/AttributeType.java 39 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DITContentRule.java 35 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DITStructureRule.java 35 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/MatchingRuleUse.java 35 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/NameForm.java 35 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/ObjectClass.java 33 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/Schema.java 443 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 29 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SchemaSynchronizationTest.java 7 ●●●● patch | view | raw | blame | history
opends/build.xml
@@ -1148,6 +1148,7 @@
      <jvmarg  value="-Demma.coverage.out.file=${coverage.data.dir}/unit.emma" />
      <jvmarg value="-Demma.coverage.out.merge=false" />
      <jvmarg value="-Dorg.opends.server.BuildRoot=${basedir}" />
      <jvmarg value="-Dorg.opends.server.RunningUnitTests=true" />
      <jvmarg value="-Dorg.opends.test.suppressOutput=${org.opends.test.suppressOutput}" />
      <jvmarg value="-Dorg.opends.test.debug.target=${org.opends.test.debug.target}" />
      <jvmarg value="-Xms${MEM}" />
opends/src/server/org/opends/server/api/SynchronizationProvider.java
@@ -28,6 +28,8 @@
import java.util.List;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
@@ -36,6 +38,7 @@
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import org.opends.server.types.Modification;
import org.opends.server.types.SynchronizationProviderResult;
@@ -368,5 +371,24 @@
  public abstract void doPostOperation(
                            ModifyDNOperation modifyDNOperation)
         throws DirectoryException;
  /**
   * Performs any processing that may be required whenever the server
   * schema has been updated.  This may be invoked for schema
   * modifications made with the server online, and it may also be
   * called if the server detects that there were any scheam changes
   * made with the server offline (e.g., by directly editing the
   * schema configuration files).
   * <BR><BR>
   * At the time this method is called, the schema changes will have
   * already been applied to the server.  As such, this method must
   * make a best effort attempt to process the associated schema
   * changes, and is not allowed to throw any exceptions.
   *
   * @param  modifications  The set of modifications that have been
   *                        made to the server schema.
   */
  public abstract void processSchemaChange(List<Modification>
                                                modifications);
}
opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -40,6 +40,7 @@
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
@@ -109,7 +110,6 @@
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.ResultCode;
import org.opends.server.types.Schema;
import org.opends.server.types.SchemaFileElement;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import org.opends.server.util.DynamicConstants;
@@ -239,8 +239,6 @@
  {
    super();
    // Perform all initialization in initializeBackend.
  }
@@ -420,6 +418,116 @@
    supportedFeatures = new HashSet<String>(0);
    // 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.
    try
    {
      // First, generate lists of elements from the current schema.
      LinkedHashSet<String> newATs  = new LinkedHashSet<String>();
      LinkedHashSet<String> newOCs  = new LinkedHashSet<String>();
      LinkedHashSet<String> newNFs  = new LinkedHashSet<String>();
      LinkedHashSet<String> newDCRs = new LinkedHashSet<String>();
      LinkedHashSet<String> newDSRs = new LinkedHashSet<String>();
      LinkedHashSet<String> newMRUs = new LinkedHashSet<String>();
      Schema.genConcatenatedSchema(newATs, newOCs, newNFs, newDCRs, newDSRs,
                                   newMRUs);
      // 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.
      String concatFilePath;
      File configFile       = new File(DirectoryServer.getConfigFile());
      File configDirectory  = configFile.getParentFile();
      File upgradeDirectory = new File(configDirectory, "upgrade");
      File concatFile       = new File(upgradeDirectory,
                                       SCHEMA_CONCAT_FILE_NAME);
      if (concatFile.exists())
      {
        concatFilePath = concatFile.getAbsolutePath();
      }
      else
      {
        concatFile = new File(upgradeDirectory,
                              SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION +
                              DynamicConstants.REVISION_NUMBER);
        if (concatFile.exists())
        {
          concatFilePath = concatFile.getAbsolutePath();
        }
        else
        {
          String runningUnitTestsStr =
               System.getProperty(PROPERTY_RUNNING_UNIT_TESTS);
          if ((runningUnitTestsStr != null) &&
              runningUnitTestsStr.equalsIgnoreCase("true"))
          {
            Schema.writeConcatenatedSchema();
            concatFile = new File(upgradeDirectory, SCHEMA_CONCAT_FILE_NAME);
            concatFilePath = concatFile.getAbsolutePath();
          }
          else
          {
            msgID = MSGID_SCHEMA_CANNOT_FIND_CONCAT_FILE;
            String message = getMessage(msgID,
                                        upgradeDirectory.getAbsolutePath(),
                                        SCHEMA_CONCAT_FILE_NAME,
                                        concatFile.getName());
            throw new InitializationException(msgID, message);
          }
        }
      }
      LinkedHashSet<String> oldATs  = new LinkedHashSet<String>();
      LinkedHashSet<String> oldOCs  = new LinkedHashSet<String>();
      LinkedHashSet<String> oldNFs  = new LinkedHashSet<String>();
      LinkedHashSet<String> oldDCRs = new LinkedHashSet<String>();
      LinkedHashSet<String> oldDSRs = new LinkedHashSet<String>();
      LinkedHashSet<String> oldMRUs = new LinkedHashSet<String>();
      Schema.readConcatenatedSchema(concatFilePath, oldATs, oldOCs, oldNFs,
                                    oldDCRs, oldDSRs, oldMRUs);
      // Create a list of modifications and add any differences between the old
      // and new schema into them.
      LinkedList<Modification> mods = new LinkedList<Modification>();
      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);
      if (! mods.isEmpty())
      {
        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)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, e);
      }
      msgID = MSGID_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES;
      String message = getMessage(msgID, stackTraceToSingleLineString(e));
      logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_ERROR, message,
               msgID);
    }
    // Register with the Directory Server as a configurable component.
    DirectoryServer.registerConfigurableComponent(this);
  }
@@ -961,13 +1069,6 @@
    }
    // At present, we only allow the addition of new attribute types,
    // object classes, name forms, DIT content rules, DIT structure rules, and
    // matching rule uses.  We will not support removing or replacing existing
    // elements, nor will we allow modification of any other attributes.  Make
    // sure that the included modify operation is acceptable within these
    // constraints.
    ArrayList<Modification> mods =
         new ArrayList<Modification>(modifyOperation.getModifications());
    if (mods.isEmpty())
@@ -978,18 +1079,19 @@
    Schema newSchema = DirectoryServer.getSchema().duplicate();
    TreeSet<String> modifiedSchemaFiles = new TreeSet<String>();
    LinkedHashSet<SchemaFileElement> dependentElements =
         new LinkedHashSet<SchemaFileElement>();
    int pos = -1;
    for (Modification m : mods)
    Iterator<Modification> iterator = mods.iterator();
    while (iterator.hasNext())
    {
      Modification m = iterator.next();
      pos++;
      if (m.isInternal())
      {
        // We don't need to do anything for internal modifications (e.g., like
        // those that set modifiersName and modifyTimestamp).
        iterator.remove();
        continue;
      }
@@ -1441,6 +1543,13 @@
      cleanUpTempSchemaFiles(tempSchemaFiles);
    }
    // Create a single file with all of the concatenated schema information
    // that we can use on startup to detect whether the schema files have been
    // edited with the server offline.
    Schema.writeConcatenatedSchema();
    DN authzDN = modifyOperation.getAuthorizationDN();
    if (authzDN == null)
    {
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -35,6 +35,7 @@
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -141,6 +142,7 @@
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.MatchingRuleUse;
import org.opends.server.types.Modification;
import org.opends.server.types.NameForm;
import org.opends.server.types.ObjectClass;
import org.opends.server.types.ObjectClassType;
@@ -435,6 +437,10 @@
  // The set of connections that are currently established.
  private LinkedHashSet<ClientConnection> establishedConnections;
  // The set of schema changes made by editing the schema configuration files
  // with the server offline.
  private List<Modification> offlineSchemaChanges;
  // The logger configuration manager for the Directory Server.
  private LoggerConfigManager loggerConfigManager;
@@ -670,6 +676,7 @@
    directoryServer.saslMechanismHandlers =
         new ConcurrentHashMap<String,SASLMechanismHandler>();
    directoryServer.authenticatedUsers = new AuthenticatedUsers();
    directoryServer.offlineSchemaChanges = new LinkedList<Modification>();
  }
@@ -2585,6 +2592,42 @@
  /**
   * Retrieves a list of modifications detailing any schema changes that may
   * have been made with the server offline (e.g., by directly editing the
   * schema configuration files).  Note that this information will not be
   * available until the server backends (and in particular, the schema backend)
   * have been initialized.
   *
   * @return  A list of modifications detailing any schema changes that may have
   *          been made with the server offline, or an empty list if no offline
   *          schema changes have been detected.
   */
  public static List<Modification> getOfflineSchemaChanges()
  {
    return directoryServer.offlineSchemaChanges;
  }
  /**
   * Specifies a list of modifications detailing any schema changes that may
   * have been made with the server offline.
   *
   * @param  offlineSchemaChanges  A list of modifications detailing any schema
   *                               changes that may have been made with the
   *                               server offline.  It must not be {@code null}.
   */
  public static void setOfflineSchemaChanges(List<Modification>
                                                  offlineSchemaChanges)
  {
    ensureNotNull(offlineSchemaChanges);
    directoryServer.offlineSchemaChanges = offlineSchemaChanges;
  }
  /**
   * Retrieves the set of matching rules registered with the Directory Server.
   * The mapping will be between the lowercase name or OID for each matching
   * rule and the matching rule implementation.  The same matching rule instance
opends/src/server/org/opends/server/core/SchemaConfigManager.java
@@ -31,6 +31,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.opends.server.api.AttributeSyntax;
@@ -63,6 +64,8 @@
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.ResultCode;
@@ -94,8 +97,6 @@
public class SchemaConfigManager
       implements ConfigChangeListener, ConfigAddListener, ConfigDeleteListener
{
  // The schema that has been parsed from the server configuration.
  private Schema schema;
@@ -771,6 +772,9 @@
   * @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.
   *
@@ -778,10 +782,11 @@
   *                                   the schema elements that is not related
   *                                   to the server configuration.
   */
  public static void loadSchemaFile(Schema schema, String schemaFile)
  public static List<Modification> loadSchemaFile(Schema schema,
                                                  String schemaFile)
         throws ConfigException, InitializationException
  {
    loadSchemaFile(schema, schemaFile, true);
    return loadSchemaFile(schema, schemaFile, true);
  }
@@ -800,6 +805,10 @@
   *                      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.
   *
@@ -807,8 +816,9 @@
   *                                   the schema elements that is not related
   *                                   to the server configuration.
   */
  private static void loadSchemaFile(Schema schema, String schemaFile,
                                     boolean failOnError)
  private static List<Modification> loadSchemaFile(Schema schema,
                                                   String schemaFile,
                                                   boolean failOnError)
         throws ConfigException, InitializationException
  {
    // Create an LDIF reader to use when reading the files.
@@ -838,7 +848,7 @@
      {
        logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_ERROR,
                 message, msgID);
        return;
        return null;
      }
    }
@@ -852,7 +862,7 @@
      if (entry == null)
      {
        // The file was empty -- skip it.
        return;
        return new LinkedList<Modification>();
      }
    }
    catch (Exception e)
@@ -874,7 +884,7 @@
      {
        logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_ERROR,
                 message, msgID);
        return;
        return null;
      }
    }
@@ -892,6 +902,7 @@
    // Get the attributeTypes attribute from the entry.
    LinkedList<Modification> mods = new LinkedList<Modification>();
    AttributeTypeSyntax attrTypeSyntax;
    try
    {
@@ -924,6 +935,13 @@
    }
    List<Attribute> attrList = entry.getAttribute(attributeAttrType);
    if ((attrList != null) && (! attrList.isEmpty()))
    {
      for (Attribute a : attrList)
      {
        mods.add(new Modification(ModificationType.ADD, a.duplicate()));
      }
    }
    // Get the objectClasses attribute from the entry.
@@ -958,6 +976,13 @@
    }
    List<Attribute> ocList = entry.getAttribute(objectclassAttrType);
    if ((ocList != null) && (! ocList.isEmpty()))
    {
      for (Attribute a : ocList)
      {
        mods.add(new Modification(ModificationType.ADD, a.duplicate()));
      }
    }
    // Get the name forms attribute from the entry.
@@ -991,6 +1016,13 @@
    }
    List<Attribute> nfList = entry.getAttribute(nameFormAttrType);
    if ((nfList != null) && (! nfList.isEmpty()))
    {
      for (Attribute a : nfList)
      {
        mods.add(new Modification(ModificationType.ADD, a.duplicate()));
      }
    }
    // Get the DIT content rules attribute from the entry.
@@ -1026,6 +1058,13 @@
    }
    List<Attribute> dcrList = entry.getAttribute(dcrAttrType);
    if ((dcrList != null) && (! dcrList.isEmpty()))
    {
      for (Attribute a : dcrList)
      {
        mods.add(new Modification(ModificationType.ADD, a.duplicate()));
      }
    }
    // Get the DIT structure rules attribute from the entry.
@@ -1061,6 +1100,13 @@
    }
    List<Attribute> dsrList = entry.getAttribute(dsrAttrType);
    if ((dsrList != null) && (! dsrList.isEmpty()))
    {
      for (Attribute a : dsrList)
      {
        mods.add(new Modification(ModificationType.ADD, a.duplicate()));
      }
    }
    // Get the matching rule uses attribute from the entry.
@@ -1096,6 +1142,14 @@
    }
    List<Attribute> mruList = entry.getAttribute(mruAttrType);
    if ((mruList != null) && (! mruList.isEmpty()))
    {
      for (Attribute a : mruList)
      {
        mods.add(new Modification(ModificationType.ADD, a.duplicate()));
      }
    }
    AttributeType synchronizationStateType =
      schema.getAttributeType(ATTR_SYNCHRONIZATION_STATE_LC);
@@ -1124,6 +1178,8 @@
          {
            attrType = attrTypeSyntax.decodeAttributeType(v.getValue(),
                                                          schema, false);
            attrType.setExtraProperty(SCHEMA_PROPERTY_FILENAME, (String) null);
            attrType.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
@@ -1221,6 +1277,8 @@
          try
          {
            oc = ocSyntax.decodeObjectClass(v.getValue(), schema, false);
            oc.setExtraProperty(SCHEMA_PROPERTY_FILENAME, (String) null);
            oc.setSchemaFile(schemaFile);
          }
          catch (DirectoryException de)
          {
@@ -1702,6 +1760,9 @@
        }
      }
    }
    return mods;
  }
opends/src/server/org/opends/server/messages/BackendMessages.java
@@ -3181,6 +3181,41 @@
  /**
   * The message ID for the message that will be used if the schema backend is
   * unable to find a file containing the concatenated schema definitions.  This
   * takes three arguments, which are the path to the directory in which the
   * file should have been found, the name of the most recent concatenated
   * schema file, and the name of the base concatenated schema file shipped with
   * the server.
   */
  public static final int MSGID_SCHEMA_CANNOT_FIND_CONCAT_FILE =
       CATEGORY_MASK_BACKEND | SEVERITY_MASK_SEVERE_ERROR | 294;
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting determine whether there were any changes made to the server
   * schema while the server was offline.  This takes a single argument, which
   * is a string representation of the exception that was caught.
   */
  public static final int MSGID_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES =
       CATEGORY_MASK_BACKEND | SEVERITY_MASK_SEVERE_ERROR | 295;
  /**
   * The message ID for the message that will be used if an error occurs while
   * trying to write a file containing the concatenated server schema.  This
   * takes two arguments, which are the path to the file being written and a
   * string representation of the exception that was caught.
   */
  public static final int MSGID_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE =
       CATEGORY_MASK_BACKEND | SEVERITY_MASK_SEVERE_ERROR | 296;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -3401,6 +3436,22 @@
                    " attribute of configuration entry %s:  %s.  The default " +
                    "behavior, which is to treat the attribute types as " +
                    "defined in the server schema, will be used.");
    registerMessage(MSGID_SCHEMA_CANNOT_FIND_CONCAT_FILE,
                    "Unable to find a file containing concatenated schema " +
                    "element definitions in order to determine if any schema " +
                    "changes were made with the server offline.  The " +
                    "file was expected in the %s directory and should have " +
                    "been named either %s or %s.");
    registerMessage(MSGID_SCHEMA_ERROR_DETERMINING_SCHEMA_CHANGES,
                    "An error occurred while attempting to determine whether " +
                    "any schema changes had been made by directly editing " +
                    "the schema files with the server offline:  %s.");
    registerMessage(MSGID_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE,
                    "An error occurred while attempting to write file %s " +
                    "containing a concatenated list of all server schema " +
                    "elements:  %s.  The server may not be able to " +
                    "accurately identify any schema changes made with the " +
                    "server offline.");
    registerMessage(MSGID_SCHEMA_ADD_NOT_SUPPORTED,
                    "Unwilling to add entry \"%s\" because add operations " +
                    "are not supported in the schema backend.");
opends/src/server/org/opends/server/messages/TaskMessages.java
@@ -202,6 +202,18 @@
  /**
   * The message ID for the shutdown message that will be used if an error
   * occurs while trying to notify a synchronization provider about the new
   * schema elements added to the server.  This takes two arguments, which are
   * the name of the synchronization provider class and a string representation
   * of the exception that was caught.
   */
  public static final int MSGID_TASK_ADDSCHEMAFILE_CANNOT_NOTIFY_SYNC_PROVIDER =
       CATEGORY_MASK_TASK | SEVERITY_MASK_SEVERE_ERROR | 17;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -245,6 +257,10 @@
    registerMessage(MSGID_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE,
                    "An error occurred while attempting to load the contents " +
                    "of schema file %s into the server schema:  %s.");
    registerMessage(MSGID_TASK_ADDSCHEMAFILE_CANNOT_NOTIFY_SYNC_PROVIDER,
                    "An error occurred while attempting to notify a " +
                    "synchronization provider of type %s about the schema " +
                    "changes made by the add schema file task:  %s.");
    registerMessage(MSGID_TASK_ADDSCHEMAFILE_CANNOT_LOCK_SCHEMA,
                    "Unable to add one or more files to the server schema " +
                    "because the server was unable to obtain a write lock on " +
opends/src/server/org/opends/server/synchronization/plugin/MultimasterSynchronization.java
@@ -120,6 +120,17 @@
        createNewSynchronizationDomain(domainEntry);
      }
    }
    /*
     * If any schema changes were made with the server offline, then handle them
     * now.
     */
    List<Modification> offlineSchemaChanges =
         DirectoryServer.getOfflineSchemaChanges();
    if ((offlineSchemaChanges != null) && (! offlineSchemaChanges.isEmpty()))
    {
      processSchemaChange(offlineSchemaChanges);
    }
  }
  /**
@@ -548,7 +559,7 @@
   *                                      applied to the schema.
   *
   */
  public static void schemaChangeNotification(List<Modification> modifications)
  public void processSchemaChange(List<Modification> modifications)
  {
    SynchronizationDomain domain =
      findDomain(DirectoryServer.getSchemaDN(), null);
opends/src/server/org/opends/server/tasks/AddSchemaFileTask.java
@@ -29,11 +29,14 @@
import java.io.File;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.locks.Lock;
import org.opends.server.api.ClientConnection;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.backends.task.Task;
import org.opends.server.backends.task.TaskState;
import org.opends.server.config.ConfigException;
@@ -43,6 +46,7 @@
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
@@ -50,13 +54,16 @@
import org.opends.server.types.ErrorLogSeverity;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LockManager;
import org.opends.server.types.Modification;
import org.opends.server.types.Privilege;
import org.opends.server.types.ResultCode;
import org.opends.server.types.Schema;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.TaskMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -68,9 +75,6 @@
public class AddSchemaFileTask
       extends Task
{
  // The list of files to be added to the server schema.
  TreeSet<String> filesToAdd;
@@ -139,6 +143,11 @@
        }
        catch (Exception e)
        {
          if (debugEnabled())
          {
            debugCaught(DebugLogLevel.ERROR, e);
          }
          int    msgID   = MSGID_TASK_ADDSCHEMAFILE_ERROR_CHECKING_FOR_FILE;
          String message = getMessage(msgID, filename, schemaDirectory,
                                      stackTraceToSingleLineString(e));
@@ -163,6 +172,11 @@
      }
      catch (ConfigException ce)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, ce);
        }
        int    msgID   = MSGID_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE;
        String message = getMessage(msgID, String.valueOf(schemaFile),
                                    ce.getMessage());
@@ -171,6 +185,11 @@
      }
      catch (InitializationException ie)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, ie);
        }
        int    msgID   = MSGID_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE;
        String message = getMessage(msgID, String.valueOf(schemaFile),
                                    ie.getMessage());
@@ -207,15 +226,53 @@
    try
    {
      LinkedList<Modification> mods = new LinkedList<Modification>();
      Schema schema = DirectoryServer.getSchema().duplicate();
      for (String schemaFile : filesToAdd)
      {
        try
        {
          SchemaConfigManager.loadSchemaFile(schema, schemaFile);
          List<Modification> modList =
               SchemaConfigManager.loadSchemaFile(schema, schemaFile);
          for (Modification m : modList)
          {
            Attribute a = m.getAttribute();
            LinkedHashSet<AttributeValue> valuesWithFileElement =
                 new LinkedHashSet<AttributeValue>();
            for (AttributeValue v : a.getValues())
            {
              String s = v.getStringValue();
              if (s.indexOf(SCHEMA_PROPERTY_FILENAME) < 0)
              {
                if (s.endsWith(" )"))
                {
                 s = s.substring(0, s.length()-1) + SCHEMA_PROPERTY_FILENAME +
                     " '" + schemaFile + "' )";
                }
                else if (s.endsWith(")"))
                {
                 s = s.substring(0, s.length()-1) + " " +
                     SCHEMA_PROPERTY_FILENAME + " '" + schemaFile + "' )";
                }
              }
              valuesWithFileElement.add(new AttributeValue(a.getAttributeType(),
                                                           s));
            }
            Attribute attrWithFile = new Attribute(a.getAttributeType(),
                                                   a.getName(),
                                                   valuesWithFileElement);
            mods.add(new Modification(m.getModificationType(), attrWithFile));
          }
        }
        catch (ConfigException ce)
        {
          if (debugEnabled())
          {
            debugCaught(DebugLogLevel.ERROR, ce);
          }
          int    msgID   = MSGID_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE;
          String message = getMessage(msgID, String.valueOf(schemaFile),
                                      ce.getMessage());
@@ -225,6 +282,11 @@
        }
        catch (InitializationException ie)
        {
          if (debugEnabled())
          {
            debugCaught(DebugLogLevel.ERROR, ie);
          }
          int    msgID   = MSGID_TASK_ADDSCHEMAFILE_ERROR_LOADING_SCHEMA_FILE;
          String message = getMessage(msgID, String.valueOf(schemaFile),
                                      ie.getMessage());
@@ -234,6 +296,33 @@
        }
      }
      if (! mods.isEmpty())
      {
        for (SynchronizationProvider provider :
             DirectoryServer.getSynchronizationProviders())
        {
          try
          {
            provider.processSchemaChange(mods);
          }
          catch (Exception e)
          {
            if (debugEnabled())
            {
              debugCaught(DebugLogLevel.ERROR, e);
            }
            int msgID = MSGID_TASK_ADDSCHEMAFILE_CANNOT_NOTIFY_SYNC_PROVIDER;
            String message = getMessage(msgID, provider.getClass().getName(),
                                        stackTraceToSingleLineString(e));
            logError(ErrorLogCategory.SCHEMA, ErrorLogSeverity.SEVERE_ERROR,
                     message, msgID);
          }
        }
        Schema.writeConcatenatedSchema();
      }
      schema.setYoungestModificationTime(System.currentTimeMillis());
      DirectoryServer.setSchema(schema);
      return TaskState.COMPLETED_SUCCESSFULLY;
opends/src/server/org/opends/server/types/AttributeType.java
@@ -40,10 +40,7 @@
import org.opends.server.core.DirectoryServer;
import org.opends.server.schema.AttributeTypeSyntax;
import static
    org.opends.server.loggers.debug.DebugLogger.debugCaught;
import static
    org.opends.server.loggers.debug.DebugLogger.debugEnabled;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
@@ -70,9 +67,6 @@
       extends CommonSchemaElements
       implements SchemaFileElement
{
  // The approximate matching rule for this attribute type.
  private final ApproximateMatchingRule approximateMatchingRule;
@@ -267,7 +261,6 @@
    ensureNotNull(definition, oid);
    this.definition   = definition;
    this.superiorType = superiorType;
    this.isCollective = isCollective;
    this.isNoUserModification = isNoUserModification;
@@ -275,6 +268,36 @@
    mayHaveSubordinateTypes = false;
    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
    if (schemaFilePos > 0)
    {
      String defStr;
      try
      {
        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
        int secondQuotePos = definition.indexOf('\'',
                                                firstQuotePos+1);
        defStr = definition.substring(0, schemaFilePos).trim() + " " +
                 definition.substring(secondQuotePos+1).trim();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        defStr = definition;
      }
      this.definition = defStr;
    }
    else
    {
      this.definition = definition;
    }
    if (syntax == null)
    {
      if (superiorType != null)
opends/src/server/org/opends/server/types/DITContentRule.java
@@ -38,6 +38,7 @@
import org.opends.server.schema.DITContentRuleSyntax;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
@@ -55,9 +56,6 @@
public final class DITContentRule
       implements SchemaFileElement
{
  // Indicates whether this content rule is declared "obsolete".
  private final boolean isObsolete;
@@ -133,11 +131,40 @@
  {
    ensureNotNull(definition, structuralClass);
    this.definition      = definition;
    this.structuralClass = structuralClass;
    this.description     = description;
    this.isObsolete      = isObsolete;
    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
    if (schemaFilePos > 0)
    {
      String defStr;
      try
      {
        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
        int secondQuotePos = definition.indexOf('\'',
                                                firstQuotePos+1);
        defStr = definition.substring(0, schemaFilePos).trim() + " " +
                 definition.substring(secondQuotePos+1).trim();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        defStr = definition;
      }
      this.definition = defStr;
    }
    else
    {
      this.definition = definition;
    }
    if ((names == null) || names.isEmpty())
    {
      this.names = new LinkedHashMap<String,String>(0);
opends/src/server/org/opends/server/types/DITStructureRule.java
@@ -38,6 +38,7 @@
import org.opends.server.schema.DITStructureRuleSyntax;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
@@ -53,9 +54,6 @@
public final class DITStructureRule
       implements SchemaFileElement
{
  // Indicates whether this DIT structure rule is declared "obsolete".
  private final boolean isObsolete;
@@ -113,12 +111,41 @@
  {
    ensureNotNull(definition);
    this.definition  = definition;
    this.ruleID      = ruleID;
    this.description = description;
    this.isObsolete  = isObsolete;
    this.nameForm    = nameForm;
    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
    if (schemaFilePos > 0)
    {
      String defStr;
      try
      {
        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
        int secondQuotePos = definition.indexOf('\'',
                                                firstQuotePos+1);
        defStr = definition.substring(0, schemaFilePos).trim() + " " +
                 definition.substring(secondQuotePos+1).trim();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        defStr = definition;
      }
      this.definition = defStr;
    }
    else
    {
      this.definition = definition;
    }
    if ((names == null) || names.isEmpty())
    {
      this.names = new LinkedHashMap<String,String>(0);
opends/src/server/org/opends/server/types/MatchingRuleUse.java
@@ -39,6 +39,7 @@
import org.opends.server.api.MatchingRule;
import org.opends.server.schema.MatchingRuleUseSyntax;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.Validator.*;
@@ -54,9 +55,6 @@
public final class MatchingRuleUse
       implements SchemaFileElement
{
  // Indicates whether this matching rule use is declared "obsolete".
  private final boolean isObsolete;
@@ -113,11 +111,40 @@
  {
    ensureNotNull(definition, matchingRule);
    this.definition   = definition;
    this.matchingRule = matchingRule;
    this.description  = description;
    this.isObsolete   = isObsolete;
    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
    if (schemaFilePos > 0)
    {
      String defStr;
      try
      {
        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
        int secondQuotePos = definition.indexOf('\'',
                                                firstQuotePos+1);
        defStr = definition.substring(0, schemaFilePos).trim() + " " +
                 definition.substring(secondQuotePos+1).trim();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        defStr = definition;
      }
      this.definition = defStr;
    }
    else
    {
      this.definition = definition;
    }
    if ((names == null) || names.isEmpty())
    {
      this.names = new LinkedHashMap<String,String>(0);
opends/src/server/org/opends/server/types/NameForm.java
@@ -38,6 +38,7 @@
import org.opends.server.schema.NameFormSyntax;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.Validator.*;
@@ -53,9 +54,6 @@
public final class NameForm
       implements SchemaFileElement
{
  // Indicates whether this name form is declared "obsolete".
  private final boolean isObsolete;
@@ -119,12 +117,41 @@
  {
    ensureNotNull(definition, oid, structuralClass);
    this.definition      = definition;
    this.oid             = oid;
    this.description     = description;
    this.isObsolete      = isObsolete;
    this.structuralClass = structuralClass;
    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
    if (schemaFilePos > 0)
    {
      String defStr;
      try
      {
        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
        int secondQuotePos = definition.indexOf('\'',
                                                firstQuotePos+1);
        defStr = definition.substring(0, schemaFilePos).trim() + " " +
                 definition.substring(secondQuotePos+1).trim();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        defStr = definition;
      }
      this.definition = defStr;
    }
    else
    {
      this.definition = definition;
    }
    if ((names == null) || names.isEmpty())
    {
      this.names = new LinkedHashMap<String,String>(0);
opends/src/server/org/opends/server/types/ObjectClass.java
@@ -39,6 +39,7 @@
import org.opends.server.schema.ObjectClassSyntax;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.Validator.*;
@@ -63,7 +64,6 @@
       extends CommonSchemaElements
       implements SchemaFileElement
{
  // The set of optional attribute types for this objectclass.
  private final Set<AttributeType> optionalAttributes;
@@ -150,9 +150,38 @@
    ensureNotNull(definition, oid);
    this.definition    = definition;
    this.superiorClass = superiorClass;
    int schemaFilePos = definition.indexOf(SCHEMA_PROPERTY_FILENAME);
    if (schemaFilePos > 0)
    {
      String defStr;
      try
      {
        int firstQuotePos = definition.indexOf('\'', schemaFilePos);
        int secondQuotePos = definition.indexOf('\'',
                                                firstQuotePos+1);
        defStr = definition.substring(0, schemaFilePos).trim() + " " +
                 definition.substring(secondQuotePos+1).trim();
      }
      catch (Exception e)
      {
        if (debugEnabled())
        {
          debugCaught(DebugLogLevel.ERROR, e);
        }
        defStr = definition;
      }
      this.definition = defStr;
    }
    else
    {
      this.definition = definition;
    }
    // Set flag indicating whether or not this object class allows any
    // attributes.
    if (hasName(OC_EXTENSIBLE_OBJECT_LC)
opends/src/server/org/opends/server/types/Schema.java
@@ -28,10 +28,17 @@
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.opends.server.api.ApproximateMatchingRule;
@@ -40,10 +47,17 @@
import org.opends.server.api.MatchingRule;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.api.SubstringMatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.SchemaConfigManager;
import org.opends.server.protocols.asn1.ASN1OctetString;
import static org.opends.server.config.ConfigConstants.*;
import static org.opends.server.loggers.debug.DebugLogger.*;
import static org.opends.server.loggers.Error.*;
import static org.opends.server.messages.BackendMessages.*;
import static org.opends.server.messages.CoreMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -2822,5 +2836,434 @@
  {
    synchronizationState = values;
  }
  /**
   * 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
    {
      LinkedHashSet<String> attributeTypes =
           new LinkedHashSet<String>();
      LinkedHashSet<String> objectClasses =
           new LinkedHashSet<String>();
      LinkedHashSet<String> nameForms = new LinkedHashSet<String>();
      LinkedHashSet<String> ditContentRules =
           new LinkedHashSet<String>();
      LinkedHashSet<String> ditStructureRules =
           new LinkedHashSet<String>();
      LinkedHashSet<String> matchingRuleUses =
           new LinkedHashSet<String>();
      genConcatenatedSchema(attributeTypes, objectClasses, nameForms,
                            ditContentRules, ditStructureRules,
                            matchingRuleUses);
      File configFile = new File(DirectoryServer.getConfigFile());
      File configDirectory  = configFile.getParentFile();
      File upgradeDirectory = new File(configDirectory, "upgrade");
      File concatFile       = new File(upgradeDirectory,
                                       SCHEMA_CONCAT_FILE_NAME);
      concatFilePath = concatFile.getAbsolutePath();
      File tempFile = new File(concatFilePath + ".tmp");
      BufferedWriter writer =
           new BufferedWriter(new FileWriter(tempFile, false));
      writer.write("dn: " + DirectoryServer.getSchemaDN().toString());
      writer.newLine();
      writer.write("objectClass: top");
      writer.newLine();
      writer.write("objectClass: ldapSubentry");
      writer.newLine();
      writer.write("objectClass: subschema");
      writer.newLine();
      for (String line : attributeTypes)
      {
        writer.write(ATTR_ATTRIBUTE_TYPES);
        writer.write(": ");
        writer.write(line);
        writer.newLine();
      }
      for (String line : objectClasses)
      {
        writer.write(ATTR_OBJECTCLASSES);
        writer.write(": ");
        writer.write(line);
        writer.newLine();
      }
      for (String line : nameForms)
      {
        writer.write(ATTR_NAME_FORMS);
        writer.write(": ");
        writer.write(line);
        writer.newLine();
      }
      for (String line : ditContentRules)
      {
        writer.write(ATTR_DIT_CONTENT_RULES);
        writer.write(": ");
        writer.write(line);
        writer.newLine();
      }
      for (String line : ditStructureRules)
      {
        writer.write(ATTR_DIT_STRUCTURE_RULES);
        writer.write(": ");
        writer.write(line);
        writer.newLine();
      }
      for (String line : matchingRuleUses)
      {
        writer.write(ATTR_MATCHING_RULE_USE);
        writer.write(": ");
        writer.write(line);
        writer.newLine();
      }
      writer.close();
      if (concatFile.exists())
      {
        concatFile.delete();
      }
      tempFile.renameTo(concatFile);
    }
    catch (Exception e)
    {
      if (debugEnabled())
      {
        debugCaught(DebugLogLevel.ERROR, 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.
      logError(ErrorLogCategory.SCHEMA,
               ErrorLogSeverity.SEVERE_WARNING,
               MSGID_SCHEMA_CANNOT_WRITE_CONCAT_SCHEMA_FILE,
               String.valueOf(concatFilePath),
               stackTraceToSingleLineString(e));
    }
  }
  /**
   * 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.
   *
   * @throws  IOException  If a problem occurs while reading the
   *                       schema file elements.
   */
  public static void genConcatenatedSchema(
                          LinkedHashSet<String> attributeTypes,
                          LinkedHashSet<String> objectClasses,
                          LinkedHashSet<String> nameForms,
                          LinkedHashSet<String> ditContentRules,
                          LinkedHashSet<String> ditStructureRules,
                          LinkedHashSet<String> matchingRuleUses)
          throws IOException
  {
    // Get a sorted list of the files in the schema directory.
    String schemaDirectory =
                SchemaConfigManager.getSchemaDirectoryPath();
    TreeSet<String> schemaFileNames = new TreeSet<String>();
    for (File f : new File(schemaDirectory).listFiles())
    {
      if (f.isFile())
      {
        schemaFileNames.add(f.getName());
      }
    }
    // Open each of the files in order and read the elements that they
    // contain, appending them to the appropriate lists.
    for (String name : schemaFileNames)
    {
      // Read the contents of the file into a list with one schema
      // element per list element.
      LinkedList<StringBuilder> lines =
           new LinkedList<StringBuilder>();
      BufferedReader reader =
           new BufferedReader(new FileReader(
                    new File(schemaDirectory, name)));
      while (true)
      {
        String line = reader.readLine();
        if (line == null)
        {
          break;
        }
        else if (line.startsWith("#") || (line.length() == 0))
        {
          continue;
        }
        else if (line.startsWith(" "))
        {
          lines.getLast().append(line.substring(1));
        }
        else
        {
          lines.add(new StringBuilder(line));
        }
      }
      reader.close();
      // Iterate through each line in the list.  Find the colon and
      // get the attribute name at the beginning.  If it's someting
      // 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)
      {
        // Get the line and add the X-SCHEMA-FILE extension to the end
        // of it.  All of them should end with " )" but some might
        // have the parenthesis crammed up against the last character
        // so deal with that as well.
        String line = buffer.toString().trim();
        if (line.endsWith(" )"))
        {
         line = line.substring(0, line.length()-1) +
                SCHEMA_PROPERTY_FILENAME + " '" + name + "' )";
        }
        else if (line.endsWith(")"))
        {
         line = line.substring(0, line.length()-1) + " " +
                SCHEMA_PROPERTY_FILENAME + " '" + name + "' )";
        }
        else
        {
          continue;
        }
        String value;
        String lowerLine = toLowerCase(line);
        if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
        {
          value = line.substring(
                       ATTR_ATTRIBUTE_TYPES.length()+1).trim();
          attributeTypes.add(value);
        }
        else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
        {
          value = line.substring(
                       ATTR_OBJECTCLASSES.length()+1).trim();
          objectClasses.add(value);
        }
        else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
        {
          value = line.substring(ATTR_NAME_FORMS.length()+1).trim();
          nameForms.add(value);
        }
        else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
        {
          value = line.substring(
                     ATTR_DIT_CONTENT_RULES.length()+1).trim();
          ditContentRules.add(value);
        }
        else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
        {
          value = line.substring(
                     ATTR_DIT_STRUCTURE_RULES.length()+1).trim();
          ditStructureRules.add(value);
        }
        else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
        {
          value = line.substring(
                     ATTR_MATCHING_RULE_USE.length()+1).trim();
          matchingRuleUses.add(value);
        }
      }
    }
  }
  /**
   * Reads data from the specified concatenated schema file into the
   * provided sets.
   *
   * @param  concatSchemaFile   The path to 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.
   *
   * @throws  IOException  If a problem occurs while reading the
   *                       schema file elements.
   */
  public static void readConcatenatedSchema(String concatSchemaFile,
                          LinkedHashSet<String> attributeTypes,
                          LinkedHashSet<String> objectClasses,
                          LinkedHashSet<String> nameForms,
                          LinkedHashSet<String> ditContentRules,
                          LinkedHashSet<String> ditStructureRules,
                          LinkedHashSet<String> matchingRuleUses)
          throws IOException
  {
    BufferedReader reader =
         new BufferedReader(new FileReader(concatSchemaFile));
    while (true)
    {
      String line = reader.readLine();
      if (line == null)
      {
        break;
      }
      String value;
      String lowerLine = toLowerCase(line);
      if (lowerLine.startsWith(ATTR_ATTRIBUTE_TYPES_LC))
      {
        value =
             line.substring(ATTR_ATTRIBUTE_TYPES.length()+1).trim();
        attributeTypes.add(value);
      }
      else if (lowerLine.startsWith(ATTR_OBJECTCLASSES_LC))
      {
        value = line.substring(ATTR_OBJECTCLASSES.length()+1).trim();
        objectClasses.add(value);
      }
      else if (lowerLine.startsWith(ATTR_NAME_FORMS_LC))
      {
        value = line.substring(ATTR_NAME_FORMS.length()+1).trim();
        nameForms.add(value);
      }
      else if (lowerLine.startsWith(ATTR_DIT_CONTENT_RULES_LC))
      {
        value = line.substring(
                     ATTR_DIT_CONTENT_RULES.length()+1).trim();
        ditContentRules.add(value);
      }
      else if (lowerLine.startsWith(ATTR_DIT_STRUCTURE_RULES_LC))
      {
        value = line.substring(
                     ATTR_DIT_STRUCTURE_RULES.length()+1).trim();
        ditStructureRules.add(value);
      }
      else if (lowerLine.startsWith(ATTR_MATCHING_RULE_USE_LC))
      {
        value = line.substring(
                     ATTR_MATCHING_RULE_USE.length()+1).trim();
        matchingRuleUses.add(value);
      }
    }
    reader.close();
  }
  /**
   * 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(
                          LinkedHashSet<String> oldElements,
                          LinkedHashSet<String> newElements,
                          AttributeType elementType,
                          LinkedList<Modification> mods)
  {
    AttributeType attributeTypesType =
         DirectoryServer.getAttributeType(ATTR_ATTRIBUTE_TYPES_LC,
                                          true);
    LinkedHashSet<AttributeValue> values =
         new LinkedHashSet<AttributeValue>();
    for (String s : oldElements)
    {
      if (! newElements.contains(s))
      {
        values.add(new AttributeValue(attributeTypesType, s));
      }
    }
    if (! values.isEmpty())
    {
      mods.add(new Modification(ModificationType.DELETE,
                        new Attribute(elementType,
                                      elementType.getNameOrOID(),
                                      values)));
    }
    values.clear();
    for (String s : newElements)
    {
      if (! oldElements.contains(s))
      {
        values.add(new AttributeValue(attributeTypesType, s));
      }
    }
    if (! values.isEmpty())
    {
      mods.add(new Modification(ModificationType.ADD,
                        new Attribute(elementType,
                                      elementType.getNameOrOID(),
                                      values)));
    }
  }
}
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2232,6 +2232,15 @@
  /**
   * The name of the system property that can be used to determine whether the
   * Directory Server is starting up for the purpose of running the unit tests.
   */
  public static final String PROPERTY_RUNNING_UNIT_TESTS =
       "org.opends.server.RunningUnitTests";
  /**
   * The name of the system property that can be used to specify the path to the
   * directory in which the schema configuration files may be found.  If this is
   * not set, then the server wiill use a directory named "schema" below the
@@ -2281,5 +2290,25 @@
   * The column at which to wrap long lines of output in the command-line tools.
   */
  public static final int MAX_LINE_WIDTH = 79;
  /**
   * The name that should be used for the file to which the latest complete
   * schema data should be concatenated.
   */
  public static final String SCHEMA_CONCAT_FILE_NAME = "schema.ldif.current";
  /**
   * The name that should be used for the concatenated schema file generated at
   * build time with the base schema for the Subversion revision on which the
   * current build is based.  The value of
   * {@code DynamicConstants.REVISION_NUMBER} must be appended to this value in
   * order to get the full name.
   */
  public static final String SCHEMA_BASE_FILE_NAME_WITHOUT_REVISION  =
       "schema.ldif.";
}
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SchemaSynchronizationTest.java
@@ -38,6 +38,7 @@
import java.util.List;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.SynchronizationProvider;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.Operation;
@@ -276,7 +277,11 @@
    Modification mod = new Modification(ModificationType.ADD, attr);
    mods.add(mod);
    MultimasterSynchronization.schemaChangeNotification(mods);
    for (SynchronizationProvider provider :
         DirectoryServer.getSynchronizationProviders())
    {
      provider.processSchemaChange(mods);
    }
    // receive the message on the broker side.
    SynchronizationMessage msg = broker.receive();