From c36a6780c20f526df1bc6e1a3a3b71dfa8b9ec3d Mon Sep 17 00:00:00 2001
From: gbellato <gbellato@localhost>
Date: Wed, 28 Feb 2007 14:06:57 +0000
Subject: [PATCH] This set of changes allow to have the schema synchronization working by configuring synchronization for suffix cn=schema (issue 613) .

---
 opends/resource/schema/02-config.ldif                                                                       |    3 
 opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java                       |   76 ++++-
 opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java                 |    4 
 opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SchemaSynchronizationTest.java  |  237 ++++++++++++++++++
 opends/src/server/org/opends/server/synchronization/plugin/PersistentServerState.java                       |   95 ------
 opends/src/server/org/opends/server/types/Schema.java                                                       |   30 ++
 opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java        |   15 -
 opends/tests/unit-tests-testng/src/server/org/opends/server/api/ConfigurableComponentTestCase.java          |    8 
 opends/src/server/org/opends/server/core/SchemaConfigManager.java                                           |   13 +
 opends/src/server/org/opends/server/core/Operation.java                                                     |   35 ++
 opends/src/server/org/opends/server/synchronization/plugin/MultimasterSynchronization.java                  |   54 +--
 opends/src/server/org/opends/server/backends/SchemaBackend.java                                             |   48 +++
 opends/src/server/org/opends/server/config/ConfigConstants.java                                             |    6 
 opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/PersistentStateTest.java |  125 +++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java    |   14 
 15 files changed, 595 insertions(+), 168 deletions(-)

diff --git a/opends/resource/schema/02-config.ldif b/opends/resource/schema/02-config.ldif
index 403c034..98110f6 100644
--- a/opends/resource/schema/02-config.ldif
+++ b/opends/resource/schema/02-config.ldif
@@ -776,7 +776,8 @@
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
   SINGLE-VALUE X-ORIGIN 'OpenDS Directory Server' )
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.227 NAME 'ds-sync-state'
-  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'OpenDS Directory Server' )
+  SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 USAGE directoryOperation
+  X-ORIGIN 'OpenDS Directory Server' )
 attributeTypes: ( 1.3.6.1.4.1.26027.1.1.228
   NAME 'ds-cfg-backup-directory' EQUALITY caseExactMatch
   SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
diff --git a/opends/src/server/org/opends/server/backends/SchemaBackend.java b/opends/src/server/org/opends/server/backends/SchemaBackend.java
index 6a4a349..0d2ed35 100644
--- a/opends/src/server/org/opends/server/backends/SchemaBackend.java
+++ b/opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -189,6 +189,9 @@
   // The attribute type that will be used to include the defined name forms.
   private AttributeType nameFormsType;
 
+  // The attribute type that will be used to save the synchronization state.
+  private AttributeType synchronizationStateType;
+
   // The value containing DN of the user we'll say created the configuration.
   private AttributeValue creatorsName;
 
@@ -293,6 +296,8 @@
     matchingRuleUsesType =
          DirectoryServer.getAttributeType(ATTR_MATCHING_RULE_USE_LC, true);
     nameFormsType = DirectoryServer.getAttributeType(ATTR_NAME_FORMS_LC, true);
+    synchronizationStateType =
+      DirectoryServer.getAttributeType(ATTR_SYNCHRONIZATION_STATE_LC, true);
 
 
     // Initialize the lastmod attributes.
@@ -797,6 +802,20 @@
                                valueSet));
     operationalAttrs.put(modifyTimestampType, attrList);
 
+    //  Add the synchronization State attribute.
+    valueSet = DirectoryServer.getSchema().getSynchronizationState();
+    attr = new Attribute(synchronizationStateType,
+                         ATTR_SYNCHRONIZATION_STATE_LC, valueSet);
+    attrList = new ArrayList<Attribute>(1);
+    attrList.add(attr);
+    if (synchronizationStateType.isOperational() && (! showAllAttributes))
+    {
+      operationalAttrs.put(synchronizationStateType, attrList);
+    }
+    else
+    {
+      userAttrs.put(synchronizationStateType, attrList);
+    }
 
     // Add all the user-defined attributes.
     for (Attribute a : userDefinedAttributes)
@@ -1350,10 +1369,19 @@
 
 
         default:
-          int    msgID   = MSGID_SCHEMA_INVALID_MODIFICATION_TYPE;
-          String message = getMessage(msgID, m.getModificationType());
-          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
-                                       msgID);
+          if (!modifyOperation.isSynchronizationOperation())
+          {
+            int    msgID   = MSGID_SCHEMA_INVALID_MODIFICATION_TYPE;
+            String message = getMessage(msgID, m.getModificationType());
+            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
+                                         message, msgID);
+          }
+          else
+          {
+            if (at.equals(synchronizationStateType))
+              newSchema.setSynchronizationState(a.getValues());
+            modifiedSchemaFiles.add(FILE_USER_SCHEMA_ELEMENTS);
+          }
       }
     }
 
@@ -3438,6 +3466,18 @@
       schemaEntry.putAttribute(matchingRuleUsesType, attrList);
     }
 
+    if (schemaFile.equals(FILE_USER_SCHEMA_ELEMENTS))
+    {
+      values = schema.getSynchronizationState();
+      if (values != null)
+      {
+        ArrayList<Attribute> attrList = new ArrayList<Attribute>(1);
+        attrList.add(new Attribute(matchingRuleUsesType,
+                                 matchingRuleUsesType.getPrimaryName(),
+                                 values));
+        schemaEntry.putAttribute(matchingRuleUsesType, attrList);
+      }
+    }
 
     // Create a temporary file to which we can write the schema entry.
     File tempFile = File.createTempFile(schemaFile, "temp");
diff --git a/opends/src/server/org/opends/server/config/ConfigConstants.java b/opends/src/server/org/opends/server/config/ConfigConstants.java
index dcbb8ed..3a30ab5 100644
--- a/opends/src/server/org/opends/server/config/ConfigConstants.java
+++ b/opends/src/server/org/opends/server/config/ConfigConstants.java
@@ -1217,7 +1217,11 @@
    */
   public static final String ATTR_MATCHING_RULE_USE_LC = "matchingruleuse";
 
-
+  /**
+   * The name of the attribute that holds the sycnhronization state,
+   * formatted in lowercase.
+   */
+  public static final String ATTR_SYNCHRONIZATION_STATE_LC = "ds-sync-state";
 
   /**
    * The default maximum request size that should be used if none is specified
diff --git a/opends/src/server/org/opends/server/core/Operation.java b/opends/src/server/org/opends/server/core/Operation.java
index 5943668..36a7ef3 100644
--- a/opends/src/server/org/opends/server/core/Operation.java
+++ b/opends/src/server/org/opends/server/core/Operation.java
@@ -138,6 +138,10 @@
   // in the response to the client.
   private StringBuilder errorMessage;
 
+  // Indicates whether this operation nneds to be synchronized to
+  // other copies of the data.
+  private boolean dontSynchronizeFlag;
+
 
 
   /**
@@ -730,6 +734,21 @@
   }
 
 
+  /**
+   * Specifies whether this operation must be synchronized to other copies
+   * of the data.
+   *
+   * @param  dontSynchronize  Specifies whether this operation must be
+   *                          synchronized to other copies
+   *                          of the data.
+   */
+  public final void setDontSynchronize(boolean dontSynchronize)
+  {
+    assert debugEnter(CLASS_NAME, "setDontSynchronize",
+                      String.valueOf(dontSynchronize));
+
+    this.dontSynchronizeFlag = dontSynchronize;
+  }
 
   /**
    * Retrieves the entry for the user that should be considered the
@@ -1058,5 +1077,21 @@
    *                 operation should be appended.
    */
   public abstract void toString(StringBuilder buffer);
+
+
+  /**
+   * Indicates whether this operation needs to be synchronized to
+   * other copies of the data.
+   *
+   * @return  <CODE>true</CODE> if this operation don't need to be
+   *                            synchronized, or
+   *          <CODE>false</CODE> if it needs to be synchronized.
+   */
+  public boolean dontSynchronize()
+  {
+    assert debugEnter(CLASS_NAME, "dontSynchronize");
+
+    return dontSynchronizeFlag;
+  }
 }
 
diff --git a/opends/src/server/org/opends/server/core/SchemaConfigManager.java b/opends/src/server/org/opends/server/core/SchemaConfigManager.java
index 7aaca38..7dc5091 100644
--- a/opends/src/server/org/opends/server/core/SchemaConfigManager.java
+++ b/opends/src/server/org/opends/server/core/SchemaConfigManager.java
@@ -1053,6 +1053,19 @@
 
     List<Attribute> mruList = entry.getAttribute(mruAttrType);
 
+    AttributeType synchronizationStateType =
+      schema.getAttributeType(ATTR_SYNCHRONIZATION_STATE_LC);
+    if (synchronizationStateType == null)
+    {
+      synchronizationStateType =
+        DirectoryServer.getDefaultAttributeType(ATTR_SYNCHRONIZATION_STATE_LC,
+            new MatchingRuleUseSyntax());
+    }
+
+    List<Attribute> synchronizationState =
+      entry.getAttribute(synchronizationStateType);
+    if (synchronizationState != null && !(synchronizationState.isEmpty()))
+      schema.setSynchronizationState(synchronizationState.get(0).getValues());
 
     // Parse the attribute type definitions if there are any.
     if (attrList != null)
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/MultimasterSynchronization.java b/opends/src/server/org/opends/server/synchronization/plugin/MultimasterSynchronization.java
index 0143f89..2a3210d 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/MultimasterSynchronization.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/MultimasterSynchronization.java
@@ -38,7 +38,6 @@
 import org.opends.server.core.AddOperation;
 import org.opends.server.synchronization.changelog.Changelog;
 import org.opends.server.synchronization.common.LogMessages;
-import org.opends.server.synchronization.common.ServerState;
 import org.opends.server.types.DN;
 import org.opends.server.core.DeleteOperation;
 import org.opends.server.types.DirectoryException;
@@ -74,20 +73,6 @@
   private static Map<DN, SynchronizationDomain> domains =
     new HashMap<DN, SynchronizationDomain>() ;
 
-  /**
-   * Get the ServerState associated to the SynchronizationDomain
-   * with a given DN.
-   *
-   * @param baseDn The DN of the Synchronization Domain for which the
-   *               ServerState must be returned.
-   * @return the ServerState associated to the SynchronizationDomain
-   *         with the DN in parameter.
-   */
-  public static ServerState getServerState(DN baseDn)
-  {
-    SynchronizationDomain domain = findDomain(baseDn);
-    return domain.getServerState();
-  }
 
   /**
    * {@inheritDoc}
@@ -323,7 +308,8 @@
   public SynchronizationProviderResult handleConflictResolution(
                                                 ModifyOperation modifyOperation)
   {
-    SynchronizationDomain domain = findDomain(modifyOperation.getEntryDN());
+    SynchronizationDomain domain =
+      findDomain(modifyOperation.getEntryDN(), modifyOperation);
     if (domain == null)
       return new SynchronizationProviderResult(true);
 
@@ -337,7 +323,8 @@
   public SynchronizationProviderResult handleConflictResolution(
       AddOperation addOperation) throws DirectoryException
   {
-    SynchronizationDomain domain = findDomain(addOperation.getEntryDN());
+    SynchronizationDomain domain =
+      findDomain(addOperation.getEntryDN(), addOperation);
     if (domain == null)
       return new SynchronizationProviderResult(true);
 
@@ -351,7 +338,8 @@
   public SynchronizationProviderResult handleConflictResolution(
       DeleteOperation deleteOperation) throws DirectoryException
   {
-    SynchronizationDomain domain = findDomain(deleteOperation.getEntryDN());
+    SynchronizationDomain domain =
+      findDomain(deleteOperation.getEntryDN(), deleteOperation);
     if (domain == null)
       return new SynchronizationProviderResult(true);
 
@@ -365,7 +353,8 @@
   public SynchronizationProviderResult handleConflictResolution(
       ModifyDNOperation modifyDNOperation) throws DirectoryException
   {
-    SynchronizationDomain domain = findDomain(modifyDNOperation.getEntryDN());
+    SynchronizationDomain domain =
+      findDomain(modifyDNOperation.getEntryDN(), modifyDNOperation);
     if (domain == null)
       return new SynchronizationProviderResult(true);
 
@@ -379,8 +368,10 @@
   public SynchronizationProviderResult
       doPreOperation(ModifyOperation modifyOperation)
   {
-    SynchronizationDomain domain = findDomain(modifyOperation.getEntryDN());
-    if (domain == null)
+    DN operationDN = modifyOperation.getEntryDN();
+    SynchronizationDomain domain = findDomain(operationDN, modifyOperation);
+
+    if ((domain == null) || (!domain.solveConflict()))
       return new SynchronizationProviderResult(true);
 
     Historical historicalInformation = (Historical)
@@ -423,7 +414,8 @@
   @Override
   public SynchronizationProviderResult doPreOperation(AddOperation addOperation)
   {
-    SynchronizationDomain domain = findDomain(addOperation.getEntryDN());
+    SynchronizationDomain domain =
+      findDomain(addOperation.getEntryDN(), addOperation);
     if (domain == null)
       return new SynchronizationProviderResult(true);
 
@@ -457,8 +449,15 @@
    * @param dn The DN for which the domain must be returned.
    * @return The Synchronization domain for this DN.
    */
-  private static SynchronizationDomain findDomain(DN dn)
+  private static SynchronizationDomain findDomain(DN dn, Operation op)
   {
+    /*
+     * Don't run the special synchronization code on Operation that are
+     * specifically marked as don't synchronize.
+     */
+    if (op.dontSynchronize())
+      return null;
+
     SynchronizationDomain domain = null;
     DN temp = dn;
     do
@@ -471,13 +470,6 @@
       }
     } while (domain == null);
 
-    /*
-     * Don't apply synchronization to the special entry where the ServerState
-     * is stored.
-     */
-    if ((domain!= null) && (domain.getServerStateDN().equals(dn)))
-      return null;
-
     return domain;
   }
 
@@ -489,7 +481,7 @@
    */
   private void genericPostOperation(Operation operation, DN dn)
   {
-    SynchronizationDomain domain = findDomain(dn);
+    SynchronizationDomain domain = findDomain(dn, operation);
     if (domain == null)
       return;
 
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/PersistentServerState.java b/opends/src/server/org/opends/server/synchronization/plugin/PersistentServerState.java
index b2c5c7d..b993a45 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/PersistentServerState.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/PersistentServerState.java
@@ -35,7 +35,6 @@
 import java.util.LinkedList;
 import java.util.List;
 
-import org.opends.server.core.AddOperation;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.core.ModifyOperation;
 import org.opends.server.protocols.asn1.ASN1OctetString;
@@ -53,7 +52,6 @@
 import org.opends.server.types.Control;
 import org.opends.server.types.DN;
 import org.opends.server.types.DereferencePolicy;
-import org.opends.server.types.DirectoryException;
 import org.opends.server.types.ErrorLogCategory;
 import org.opends.server.types.ErrorLogSeverity;
 import org.opends.server.types.ModificationType;
@@ -72,8 +70,7 @@
    private boolean savedStatus = true;
    private InternalClientConnection conn =
        InternalClientConnection.getRootConnection();
-   private ASN1OctetString serverStateAsn1Dn;
-   private DN serverStateDn;
+   private ASN1OctetString asn1BaseDn;
 
    /**
     * The attribute name used to store the state in the backend.
@@ -87,16 +84,8 @@
   public PersistentServerState(DN baseDn)
   {
     this.baseDn = baseDn;
-    serverStateAsn1Dn = new ASN1OctetString(
-        "dc=ffffffff-ffffffff-ffffffff-ffffffff,"
-        + baseDn.toString());
-    try
-    {
-      serverStateDn = DN.decode(serverStateAsn1Dn);
-    } catch (DirectoryException e)
-    {
-      // never happens
-    }
+    asn1BaseDn = new ASN1OctetString(baseDn.toString());
+    loadState();
   }
 
   /**
@@ -121,22 +110,14 @@
     ResultCode resultCode = updateStateEntry();
     if (resultCode != ResultCode.SUCCESS)
     {
-      if (resultCode == ResultCode.NO_SUCH_OBJECT)
-      {
-        createStateEntry();
-      }
-      else
-      {
         savedStatus = false;
-
-      }
     }
   }
 
   /**
    * Load the ServerState from the backing entry in database to memory.
    */
-  public void loadState()
+  private void loadState()
   {
     /*
      * Read the serverState from the database,
@@ -156,10 +137,12 @@
      * Search the database entry that is used to periodically
      * save the ServerState
      */
-    InternalSearchOperation search = conn.processSearch(serverStateAsn1Dn,
+    LinkedHashSet<String> attributes = new LinkedHashSet<String>(1);
+    attributes.add(SYNCHRONIZATION_STATE);
+    InternalSearchOperation search = conn.processSearch(asn1BaseDn,
         SearchScope.BASE_OBJECT,
         DereferencePolicy.DEREF_ALWAYS, 0, 0, false,
-        filter,new LinkedHashSet<String>(0));
+        filter,attributes);
     if (((search.getResultCode() != ResultCode.SUCCESS)) &&
         ((search.getResultCode() != ResultCode.NO_SUCH_OBJECT)))
     {
@@ -210,51 +193,6 @@
        * and an ordering index for historical attribute
        */
     }
-
-    if ((resultEntry == null) ||
-        ((search.getResultCode() != ResultCode.SUCCESS)))
-    {
-      createStateEntry();
-    }
-  }
-
-  /**
-   * Create the Entry that will be used to store the ServerState information.
-   * It will be updated when the server stops and periodically.
-   */
-  private void createStateEntry()
-  {
-    ArrayList<LDAPAttribute> attrs = new ArrayList<LDAPAttribute>();
-
-    ArrayList<ASN1OctetString> values = new ArrayList<ASN1OctetString>();
-    ASN1OctetString value = new ASN1OctetString("extensibleObject");
-    values.add(value);
-    LDAPAttribute attr = new LDAPAttribute("objectClass", values);
-    value = new ASN1OctetString("domain");
-    values.add(value);
-    attr = new LDAPAttribute("objectClass", values);
-    attrs.add(attr);
-
-    values = new ArrayList<ASN1OctetString>();
-    value = new ASN1OctetString("ffffffff-ffffffff-ffffffff-ffffffff");
-    values.add(value);
-    attr = new LDAPAttribute("dc", values);
-    attrs.add(attr);
-
-    AddOperation add = conn.processAdd(serverStateAsn1Dn, attrs);
-    ResultCode resultCode = add.getResultCode();
-    if ((resultCode != ResultCode.SUCCESS) &&
-        (resultCode != ResultCode.NO_SUCH_OBJECT))
-    {
-      int msgID = MSGID_ERROR_UPDATING_RUV;
-      String message = getMessage(msgID,
-          add.getResultCode().getResultCodeName(),
-          add.toString(), add.getErrorMessage(),
-          baseDn.toString());
-      logError(ErrorLogCategory.SYNCHRONIZATION,
-          ErrorLogSeverity.SEVERE_ERROR,
-          message, msgID);
-    }
   }
 
   /**
@@ -266,8 +204,7 @@
   private ResultCode updateStateEntry()
   {
     /*
-     * Generate a modify operation on the Server State Entry :
-     * cn=ffffffff-ffffffff-ffffffff-ffffffff, baseDn
+     * Generate a modify operation on the Server State baseD Entry.
      */
     ArrayList<ASN1OctetString> values = this.toASN1ArrayList();
 
@@ -283,10 +220,11 @@
     ModifyOperation op =
       new ModifyOperation(conn, InternalClientConnection.nextOperationID(),
           InternalClientConnection.nextMessageID(),
-          new ArrayList<Control>(0), serverStateAsn1Dn,
+          new ArrayList<Control>(0), asn1BaseDn,
           mods);
     op.setInternalOperation(true);
     op.setSynchronizationOperation(true);
+    op.setDontSynchronize(true);
 
     op.run();
 
@@ -301,15 +239,4 @@
     }
     return result;
   }
-
-  /**
-   * Get the Dn where the ServerState is stored.
-   * @return Returns the serverStateDn.
-   */
-  public DN getServerStateDn()
-  {
-    return serverStateDn;
-  }
-
-
 }
diff --git a/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java b/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java
index 192eabe..8635b4b 100644
--- a/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java
+++ b/opends/src/server/org/opends/server/synchronization/plugin/SynchronizationDomain.java
@@ -139,7 +139,6 @@
 
   private short serverId;
 
-  private BooleanConfigAttribute receiveStatusStub;
   private int listenerThreadNumber = 10;
   private boolean receiveStatus = true;
 
@@ -157,16 +156,18 @@
   private InternalClientConnection conn =
       InternalClientConnection.getRootConnection();
 
-  static String CHANGELOG_SERVER_ATTR = "ds-cfg-changelog-server";
-  static String BASE_DN_ATTR = "ds-cfg-synchronization-dn";
-  static String SERVER_ID_ATTR = "ds-cfg-directory-server-id";
-  static String RECEIVE_STATUS = "ds-cfg-receive-status";
-  static String MAX_RECEIVE_QUEUE = "ds-cfg-max-receive-queue";
-  static String MAX_RECEIVE_DELAY = "ds-cfg-max-receive-delay";
-  static String MAX_SEND_QUEUE = "ds-cfg-max-send-queue";
-  static String MAX_SEND_DELAY = "ds-cfg-max-send-delay";
-  static String WINDOW_SIZE = "ds-cfg-window-size";
-  static String HEARTBEAT_INTERVAL = "ds-cfg-heartbeat-interval";
+  private boolean solveConflictFlag = true;
+
+  static final String CHANGELOG_SERVER_ATTR = "ds-cfg-changelog-server";
+  static final String BASE_DN_ATTR = "ds-cfg-synchronization-dn";
+  static final String SERVER_ID_ATTR = "ds-cfg-directory-server-id";
+  static final String RECEIVE_STATUS = "ds-cfg-receive-status";
+  static final String MAX_RECEIVE_QUEUE = "ds-cfg-max-receive-queue";
+  static final String MAX_RECEIVE_DELAY = "ds-cfg-max-receive-delay";
+  static final String MAX_SEND_QUEUE = "ds-cfg-max-send-queue";
+  static final String MAX_SEND_DELAY = "ds-cfg-max-send-delay";
+  static final String WINDOW_SIZE = "ds-cfg-window-size";
+  static final String HEARTBEAT_INTERVAL = "ds-cfg-heartbeat-interval";
 
   private static final StringConfigAttribute changelogStub =
     new StringConfigAttribute(CHANGELOG_SERVER_ATTR,
@@ -180,6 +181,10 @@
     new DNConfigAttribute(BASE_DN_ATTR, "synchronization base DN",
                           true, false, false);
 
+  private static final BooleanConfigAttribute receiveStatusStub =
+    new BooleanConfigAttribute(RECEIVE_STATUS, "receive status", false);
+
+
   /**
    * The set of time units that will be used for expressing the heartbeat
    * interval.
@@ -251,14 +256,33 @@
       baseDN = baseDn.activeValue();
     configAttributes.add(baseDn);
 
+    /*
+     * Modify conflicts are solved for all suffixes but the cn=schema suffix
+     * because we don't want to store extra information in the schema
+     * ldif files.
+     * This has no negative impact because the changes on schema should
+     * not produce conflicts.
+     */
+    try
+    {
+      if (baseDN.compareTo(DN.decode("cn=schema")) == 0)
+      {
+        solveConflictFlag = false;
+      }
+      else
+      {
+        solveConflictFlag = true;
+      }
+    } catch (DirectoryException e1)
+    {
+      // never happens because "cn=schema" is a valid DN
+    }
+
     state = new PersistentServerState(baseDN);
-    state.loadState();
 
     /*
      * Read the Receive Status.
      */
-    receiveStatusStub = new BooleanConfigAttribute(RECEIVE_STATUS,
-        "receive status", false);
     BooleanConfigAttribute receiveStatusAttr = (BooleanConfigAttribute)
           configEntry.getConfigAttribute(receiveStatusStub);
     if (receiveStatusAttr != null)
@@ -712,6 +736,8 @@
       // so this is not a synchronization operation.
       ChangeNumber changeNumber = generateChangeNumber(modifyOperation);
       String modifiedEntryUUID = Historical.getEntryUuid(modifiedEntry);
+      if (modifiedEntryUUID == null)
+        modifiedEntryUUID = modifyOperation.getEntryDN().toString();
       ctx = new ModifyContext(changeNumber, modifiedEntryUUID);
       modifyOperation.setAttachment(SYNCHROCONTEXT, ctx);
     }
@@ -719,7 +745,8 @@
     {
       String modifiedEntryUUID = ctx.getEntryUid();
       String currentEntryUUID = Historical.getEntryUuid(modifiedEntry);
-      if (!currentEntryUUID.equals(modifiedEntryUUID))
+      if ((currentEntryUUID != null) &&
+          (!currentEntryUUID.equals(modifiedEntryUUID)))
       {
         /*
          * The current modified entry is not the same entry as the one on
@@ -1119,15 +1146,6 @@
   }
 
   /**
-   * Get the DN where the ServerState is stored.
-   * @return The DN where the ServerState is stored.
-   */
-  public DN getServerStateDN()
-  {
-    return state.getServerStateDn();
-  }
-
-  /**
    * Get the name of the changelog server to which this domain is currently
    * connected.
    *
@@ -1827,4 +1845,14 @@
   {
     return broker.getNumLostConnections();
   }
+
+  /**
+   * Check if the domain solve conflicts.
+   *
+   * @return a boolean indicating if the domain should sove conflicts.
+   */
+  public boolean solveConflict()
+  {
+    return solveConflictFlag;
+  }
 }
diff --git a/opends/src/server/org/opends/server/types/Schema.java b/opends/src/server/org/opends/server/types/Schema.java
index ddc5531..8eda996 100644
--- a/opends/src/server/org/opends/server/types/Schema.java
+++ b/opends/src/server/org/opends/server/types/Schema.java
@@ -188,6 +188,9 @@
   // file.
   private long youngestModificationTime;
 
+  // The Synchronization State.
+  private LinkedHashSet<AttributeValue> synchronizationState = null;
+
 
 
   /**
@@ -3030,8 +3033,35 @@
     dupSchema.objectClassSet.addAll(objectClassSet);
     dupSchema.oldestModificationTime   = oldestModificationTime;
     dupSchema.youngestModificationTime = youngestModificationTime;
+    if (synchronizationState != null)
+    {
+      dupSchema.synchronizationState =
+        new LinkedHashSet<AttributeValue>(synchronizationState);
+    }
 
     return dupSchema;
   }
+
+
+  /**
+   * Retrieves the Synchronization state for this schema.
+   *
+   * @return  The Synchronization state for this schema.
+   */
+  public LinkedHashSet<AttributeValue> getSynchronizationState()
+  {
+    return synchronizationState;
+  }
+
+  /**
+   * Sets the Synchronization state for this schema.
+   *
+   * @param  values  Synchronization state for this schema.
+   */
+  public void setSynchronizationState(
+              LinkedHashSet<AttributeValue> values)
+  {
+    synchronizationState = values;
+  }
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/api/ConfigurableComponentTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/api/ConfigurableComponentTestCase.java
index efbbead..0aceb29 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/api/ConfigurableComponentTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/api/ConfigurableComponentTestCase.java
@@ -137,8 +137,12 @@
     DN          configEntryDN = c.getConfigurableComponentEntryDN();
     ConfigEntry configEntry   = DirectoryServer.getConfigEntry(configEntryDN);
 
-    ArrayList<String> unacceptableReasons = new ArrayList<String>();
-    assertTrue(c.hasAcceptableConfiguration(configEntry, unacceptableReasons));
+    if (configEntry != null)
+    {
+      ArrayList<String> unacceptableReasons = new ArrayList<String>();
+      assertTrue(c.hasAcceptableConfiguration(configEntry,
+                                              unacceptableReasons));
+    }
   }
 }
 
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SchemaSynchronizationTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SchemaSynchronizationTest.java
new file mode 100644
index 0000000..6215ca6
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SchemaSynchronizationTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.synchronization;
+
+import static org.opends.server.loggers.Error.logError;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.core.ModifyOperation;
+import org.opends.server.core.Operation;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.protocols.ldap.LDAPModification;
+import org.opends.server.synchronization.common.ChangeNumber;
+import org.opends.server.synchronization.plugin.ChangelogBroker;
+import org.opends.server.synchronization.protocol.ModifyMsg;
+import org.opends.server.synchronization.protocol.SynchronizationMessage;
+import org.opends.server.types.Attribute;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DN;
+import org.opends.server.types.ErrorLogCategory;
+import org.opends.server.types.ErrorLogSeverity;
+import org.opends.server.types.Modification;
+import org.opends.server.types.ModificationType;
+import org.opends.server.types.ResultCode;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Test for the schema synchronization.
+ */
+public class SchemaSynchronizationTest extends SynchronizationTestCase
+{
+
+  ArrayList<Modification> rcvdMods = null;
+
+  /**
+   * Set up the environment for performing the tests in this Class.
+   * synchronization
+   *
+   * @throws Exception
+   *           If the environment could not be set up.
+   */
+  @BeforeClass
+  public void setUp() throws Exception
+  {
+    // This test suite depends on having the schema available.
+    TestCaseUtils.startServer();
+
+    // Disable schema check
+    schemaCheck = DirectoryServer.checkSchema();
+    DirectoryServer.setCheckSchema(false);
+
+    // Create an internal connection
+    connection = InternalClientConnection.getRootConnection();
+
+    // top level synchro provider
+    String synchroStringDN = "cn=Synchronization Providers,cn=config";
+
+    // Multimaster Synchro plugin
+    synchroPluginStringDN = "cn=Multimaster Synchronization, "
+        + synchroStringDN;
+    String synchroPluginLdif = "dn: "
+        + synchroPluginStringDN
+        + "\n"
+        + "objectClass: top\n"
+        + "objectClass: ds-cfg-synchronization-provider\n"
+        + "ds-cfg-synchronization-provider-enabled: true\n"
+        + "ds-cfg-synchronization-provider-class: org.opends.server.synchronization.MultimasterSynchronization\n";
+    synchroPluginEntry = TestCaseUtils.entryFromLdifString(synchroPluginLdif);
+
+    // Change log
+    String changeLogStringDN = "cn=Changelog Server, " + synchroPluginStringDN;
+    String changeLogLdif = "dn: " + changeLogStringDN + "\n"
+        + "objectClass: top\n"
+        + "objectClass: ds-cfg-synchronization-changelog-server-config\n"
+        + "cn: Changelog Server\n" + "ds-cfg-changelog-port: 8989\n"
+        + "ds-cfg-changelog-server-id: 1\n";
+    changeLogEntry = TestCaseUtils.entryFromLdifString(changeLogLdif);
+
+    // suffix synchronized
+    String synchroServerLdif = "dn: cn=example, " + synchroPluginStringDN + "\n"
+        + "objectClass: top\n"
+        + "objectClass: ds-cfg-synchronization-provider-config\n"
+        + "cn: example\n"
+        + "ds-cfg-synchronization-dn: cn=schema\n"
+        + "ds-cfg-changelog-server: localhost:8989\n"
+        + "ds-cfg-directory-server-id: 1\n";
+    synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif);
+
+    configureSynchronization();
+  }
+
+  /**
+   * Checks that changes done to the schema are pushed to the changelog
+   * clients.
+   */
+  @Test()
+  public void pushSchemaChange() throws Exception
+  {
+    logError(ErrorLogCategory.SYNCHRONIZATION,
+        ErrorLogSeverity.NOTICE,
+        "Starting synchronization test : pushSchemaChange ", 1);
+
+    final DN baseDn = DN.decode("cn=schema");
+
+    ChangelogBroker broker =
+      openChangelogSession(baseDn, (short) 2, 100, 8989, 1000, true);
+
+    try
+    {
+      // Modify the schema
+      AttributeType attrType =
+        DirectoryServer.getAttributeType("attributetypes", true);
+      LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>();
+      values.add(new AttributeValue(attrType, "( 2.5.44.77.33 NAME 'dummy' )"));
+      Attribute attr = new Attribute(attrType, "attributetypes", values);
+      List<Modification> mods = new ArrayList<Modification>();
+      Modification mod = new Modification(ModificationType.ADD, attr);
+      mods.add(mod);
+      ModifyOperation modOp = new ModifyOperation(connection,
+          InternalClientConnection.nextOperationID(), InternalClientConnection
+          .nextMessageID(), null, baseDn, mods);
+      modOp.setInternalOperation(true);
+      modOp.run();
+
+      ResultCode code = modOp.getResultCode();
+      assertTrue(code.equals(ResultCode.SUCCESS),
+                 "The original operation failed");
+
+      // See if the client has received the msg
+      SynchronizationMessage msg = broker.receive();
+
+      assertTrue(msg instanceof ModifyMsg,
+                 "The received synchronization message is not a MODIFY msg");
+      ModifyMsg modMsg = (ModifyMsg) msg;
+
+      Operation receivedOp = modMsg.createOperation(connection);
+      assertTrue(DN.decode(modMsg.getDn()).compareTo(baseDn) == 0,
+                 "The received message is not for cn=schema");
+
+      assertTrue(receivedOp instanceof ModifyOperation,
+                 "The received synchronization message is not a MODIFY msg");
+      ModifyOperation receivedModifyOperation = (ModifyOperation) receivedOp;
+
+      List<LDAPModification> rcvdRawMods =
+        receivedModifyOperation.getRawModifications();
+
+      this.rcvdMods = new ArrayList<Modification>();
+      for (LDAPModification m : rcvdRawMods)
+      {
+        this.rcvdMods.add(m.toModification());
+      }
+
+      assertTrue(this.rcvdMods.contains(mod),
+                 "The received mod does not contain the original change");
+
+      /*
+       * Now cleanup the schema for the next test
+       */
+      mod = new Modification(ModificationType.DELETE, attr);
+      mods.clear();
+      mods.add(mod);
+      modOp = new ModifyOperation(connection,
+          InternalClientConnection.nextOperationID(), InternalClientConnection
+          .nextMessageID(), null, baseDn, mods);
+      modOp.setInternalOperation(true);
+      modOp.run();
+
+      code = modOp.getResultCode();
+      assertTrue(code.equals(ResultCode.SUCCESS),
+                 "The original operation failed");
+    }
+    finally
+    {
+      broker.stop();
+    }
+  }
+
+  /**
+   * Checks that changes to the schema pushed to the changelog
+   * are received and correctly replayed by synchronization plugin.
+   */
+  @Test(dependsOnMethods = { "pushSchemaChange" })
+  public void replaySchemaChange() throws Exception
+  {
+    logError(ErrorLogCategory.SYNCHRONIZATION,
+        ErrorLogSeverity.NOTICE,
+        "Starting synchronization test : pushSchemaChange ", 1);
+
+    final DN baseDn = DN.decode("cn=schema");
+
+    ChangelogBroker broker =
+      openChangelogSession(baseDn, (short) 2, 100, 8989, 1000, true);
+
+    ModifyMsg modMsg = new ModifyMsg(new ChangeNumber((long) 10, 1, (short) 2),
+                                     baseDn, rcvdMods, "cn=schema");
+    broker.publish(modMsg);
+
+    boolean found = checkEntryHasAttribute(baseDn, "attributetypes",
+                                           "( 2.5.44.77.33 NAME 'dummy' )",
+                                           10000, true);
+
+    if (found == false)
+      fail("The modification has not been correctly replayed.");
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java
index 65f3eaf..1228864 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/StressTest.java
@@ -65,8 +65,7 @@
 import org.testng.annotations.Test;
 
 /**
- * Test the contructors, encoders and decoders of the synchronization AckMsg,
- * ModifyMsg, ModifyDnMsg, AddMsg and Delete Msg
+ * Stress test for the synchronization code using the ChangelogBroker API.
  */
 public class StressTest extends SynchronizationTestCase
 {
@@ -109,7 +108,6 @@
 
     final DN baseDn = DN.decode("ou=People,dc=example,dc=com");
     final int TOTAL_MESSAGES = 1000;
-    cleanEntries();
 
     ChangelogBroker broker =
       openChangelogSession(baseDn, (short) 18, 100, 8989, 5000, true);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
index 4ad583d..3d69c5c 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/SynchronizationTestCase.java
@@ -129,9 +129,12 @@
       int port, int timeout, boolean emptyOldChanges)
           throws Exception, SocketException
   {
-    PersistentServerState state = new PersistentServerState(baseDn);
+    ServerState state;
     if (emptyOldChanges)
-      state.loadState();
+       state = new PersistentServerState(baseDn);
+    else
+       state = new ServerState();
+
     ChangelogBroker broker = new ChangelogBroker(
         state, baseDn, serverId, 0, 0, 0, 0, window_size, 0);
     ArrayList<String> servers = new ArrayList<String>(1);
@@ -188,9 +191,12 @@
       boolean emptyOldChanges)
           throws Exception, SocketException
   {
-    PersistentServerState state = new PersistentServerState(baseDn);
+    ServerState state;
     if (emptyOldChanges)
-      state.loadState();
+       state = new PersistentServerState(baseDn);
+    else
+       state = new ServerState();
+
     ChangelogBroker broker = new ChangelogBroker(
         state, baseDn, serverId, maxRcvQueue, 0,
         maxSendQueue, 0, window_size, 0);
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
index 031de12..c80b565 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/UpdateOperationTest.java
@@ -123,7 +123,6 @@
           entry.getUserAttributes(), entry.getOperationalAttributes());
       addOp.setInternalOperation(true);
       addOp.run();
-      entryList.add(entry.getDN());
     }
 
     baseUUID = getEntryUUID(DN.decode("ou=People,dc=example,dc=com"));
@@ -819,6 +818,7 @@
   {
     return new Object[][] { { false }, {true} };
   }
+
   /**
    * Tests done using directly the ChangelogBroker interface.
    */
@@ -839,19 +839,6 @@
       ChangeNumberGenerator gen = new ChangeNumberGenerator((short) 27, 0);
 
       /*
-       * loop receiving update until there is nothing left
-       * to make sure that message from previous tests have been consumed.
-       */
-      try
-      {
-        while (true)
-        {
-          broker.receive();
-        }
-      }
-      catch (Exception e)
-      {}
-      /*
        * Test that operations done on this server are sent to the
        * changelog server and forwarded to our changelog broker session.
        */
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/PersistentStateTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/PersistentStateTest.java
new file mode 100644
index 0000000..cbe6672
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/PersistentStateTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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
+ *
+ *
+ *      Portions Copyright 2006 Sun Microsystems, Inc.
+ */
+package org.opends.server.synchronization.plugin;
+
+import static org.testng.Assert.assertEquals;
+
+import org.opends.server.TestCaseUtils;
+import org.opends.server.core.AddOperation;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.synchronization.SynchronizationTestCase;
+import org.opends.server.synchronization.common.ChangeNumber;
+import org.opends.server.synchronization.common.ChangeNumberGenerator;
+import org.opends.server.types.DN;
+import org.opends.server.types.Entry;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/**
+ * Test the PersistentServerState class.
+ */
+public class PersistentStateTest extends SynchronizationTestCase
+{
+  /**
+   * Set up the environment for performing the tests in this suite.
+   *
+   * @throws Exception
+   *         If the environment could not be set up.
+   */
+  @BeforeClass
+  public void setUp() throws Exception
+  {
+    /*
+     * start the server and create the dc=exmaple,dc=xom entry if it does not
+     * exist yet.
+     */
+    TestCaseUtils.startServer();
+    String topEntry = "dn: dc=example,dc=com\n" + "objectClass: top\n"
+    + "objectClass: domain\n";
+
+    connection = InternalClientConnection.getRootConnection();
+    Entry entry = TestCaseUtils.entryFromLdifString(topEntry);
+    AddOperation addOp = new AddOperation(connection,
+        InternalClientConnection.nextOperationID(), InternalClientConnection
+        .nextMessageID(), null, entry.getDN(), entry.getObjectClasses(),
+        entry.getUserAttributes(), entry.getOperationalAttributes());
+    addOp.setInternalOperation(true);
+    addOp.run();
+  }
+
+  /**
+   * The suffix for which we want to test the PersistentServerState class.
+   */
+  @DataProvider(name = "suffix")
+  public Object[][] suffixData() {
+    return new Object[][] {
+       {"dc=example,dc=com"},
+       {"cn=schema"}
+    };
+  }
+
+  /**
+   * Test that the PersistentServerState class is able to store and
+   * retrieve ServerState to persistent storage.
+   */
+  @Test(dataProvider = "suffix")
+  public void persistenServerStateTest(String dn)
+         throws Exception
+  {
+    /*
+     * Create a new PersitentServerState,
+     * update it with 2 new ChangeNumbers with 2 different server Ids
+     * save it
+     *
+     * Then creates a new PersistentServerState and check that the
+     * 2 ChangeNumbers have been saved in this new PersistentServerState.
+     */
+    DN baseDn = DN.decode(dn);
+    PersistentServerState state = new PersistentServerState(baseDn);
+    ChangeNumberGenerator gen1 = new ChangeNumberGenerator((short) 1, state);
+    ChangeNumberGenerator gen2 = new ChangeNumberGenerator((short) 2, state);
+
+    ChangeNumber cn1 = gen1.NewChangeNumber();
+    ChangeNumber cn2 = gen2.NewChangeNumber();
+
+    state.update(cn1);
+    state.update(cn2);
+
+    state.save();
+
+    PersistentServerState stateSaved = new PersistentServerState(baseDn);
+    ChangeNumber cn1Saved = stateSaved.getMaxChangeNumber((short) 1);
+    ChangeNumber cn2Saved = stateSaved.getMaxChangeNumber((short) 2);
+
+    assertEquals(cn1Saved, cn1,
+        "cn1 has not been saved or loaded correctly for " + dn);
+    assertEquals(cn2Saved, cn2,
+        "cn2 has not been saved or loaded correctly for " + dn);
+
+  }
+}

--
Gitblit v1.10.0