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

coulbeck
07.51.2007 15d537d27c0116138191b0a60ac03b54eb00d4e5
An extra synchronization unit test and a number of bug fixes for attributes with options.
1 files added
4 files modified
388 ■■■■ changed files
opends/src/server/org/opends/server/synchronization/plugin/HistVal.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/synchronization/plugin/Historical.java 193 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/LDIFReader.java 18 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java 166 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/synchronization/plugin/HistVal.java
@@ -89,7 +89,7 @@
      String[] optionsToken = token[0].split(";");
      options = new HashSet<String>();
      int index = 1;
      while (optionsToken[index] != null)
      while (index < optionsToken.length)
      {
        options.add(optionsToken[index]);
        index ++;
opends/src/server/org/opends/server/synchronization/plugin/Historical.java
@@ -96,6 +96,17 @@
                           = new HashMap<AttributeType,AttrInfoWithOptions>();
  /**
   * {@inheritDoc}
   */
  @Override
  public String toString()
  {
    StringBuilder builder = new StringBuilder();
    builder.append(encode());
    return builder.toString();
  }
  /**
   * Duplicates an Historical Object.
   * attributesInfo are nor duplicated but used as references.
   * @return The duplicate of the Historical Object
@@ -131,12 +142,16 @@
      Attribute modAttr = m.getAttribute();
      Set<String> options = modAttr.getOptions();
      if (options.isEmpty())
      {
        options = null;
      }
      AttributeType type = modAttr.getAttributeType();
      AttrInfoWithOptions attrInfoWithOptions =  attributesInfo.get(type);
      AttrInfo attrInfo = null;
      if (attrInfoWithOptions != null)
      {
        attrInfo = attrInfoWithOptions.get(options);
      }
      if (this.hasConflict(attrInfo, changeNumber))
      {
@@ -148,68 +163,72 @@
        switch (m.getModificationType())
        {
        case DELETE:
          if (changeNumber.older(attrInfo.getDeleteTime()))
          {
            /* this delete is already obsoleted by a more recent delete
             * skip this mod
             */
            modsIterator.remove();
          case DELETE:
            if (changeNumber.older(attrInfo.getDeleteTime()))
            {
              /* this delete is already obsoleted by a more recent delete
              * skip this mod
              */
              modsIterator.remove();
              break;
            }
            this.conflictDelete(changeNumber,
                                type, m, modifiedEntry, attrInfo, modAttr);
            break;
          }
          this.conflictDelete(changeNumber,
              type, m, modifiedEntry, attrInfo, modAttr);
          break;
        case ADD:
          this.conflictAdd(modsIterator, changeNumber, attrInfo,
              modAttr.getValues(), modAttr.getOptions());
          break;
        case REPLACE:
          if (changeNumber.older(attrInfo.getDeleteTime()))
          {
            /* this replace is already obsoleted by a more recent delete
             * skip this mod
             */
            modsIterator.remove();
          case ADD:
            this.conflictAdd(modsIterator, changeNumber, attrInfo,
                             modAttr.getValues(), modAttr.getOptions());
            break;
          }
          /* save the values that are added by the replace operation
           * into addedValues
           * first process the replace as a delete operation -> this generate
           * a list of values that should be kept
           * then process the addedValues as if they were coming from a add
           * -> this generate the list of values that needs to be added
           * concatenate the 2 generated lists into a replace
           */
          LinkedHashSet<AttributeValue> addedValues  = modAttr.getValues();
          modAttr.setValues(new LinkedHashSet<AttributeValue>());
          this.conflictDelete(changeNumber, type, m, modifiedEntry,
              attrInfo, modAttr);
          case REPLACE:
            if (changeNumber.older(attrInfo.getDeleteTime()))
            {
              /* this replace is already obsoleted by a more recent delete
              * skip this mod
              */
              modsIterator.remove();
              break;
            }
            /* save the values that are added by the replace operation
            * into addedValues
            * first process the replace as a delete operation -> this generate
            * a list of values that should be kept
            * then process the addedValues as if they were coming from a add
            * -> this generate the list of values that needs to be added
            * concatenate the 2 generated lists into a replace
            */
            LinkedHashSet<AttributeValue> addedValues = modAttr.getValues();
            modAttr.setValues(new LinkedHashSet<AttributeValue>());
          LinkedHashSet<AttributeValue> keptValues = modAttr.getValues();
          this.conflictAdd(modsIterator, changeNumber, attrInfo, addedValues,
              modAttr.getOptions());
          keptValues.addAll(addedValues);
          break;
            this.conflictDelete(changeNumber, type, m, modifiedEntry,
                                attrInfo, modAttr);
        case INCREMENT:
          // TODO : FILL ME
          break;
            LinkedHashSet<AttributeValue> keptValues = modAttr.getValues();
            this.conflictAdd(modsIterator, changeNumber, attrInfo, addedValues,
                             modAttr.getOptions());
            keptValues.addAll(addedValues);
            break;
          case INCREMENT:
            // TODO : FILL ME
            break;
        }
      }
      else
      {
        processLocalOrNonConflictModification(changeNumber, m);
      }
    }
    // TODO : now purge old historical information
    if (moreRecentChangenumber == null ||
        moreRecentChangenumber.older(changeNumber))
         moreRecentChangenumber.older(changeNumber))
    {
      moreRecentChangenumber = changeNumber;
    }
  }
  /**
@@ -240,17 +259,25 @@
    Attribute modAttr = mod.getAttribute();
    if (modAttr.getAttributeType().equals(historicalAttrType))
    {
      return;
    }
    Set<String> options = modAttr.getOptions();
    if (options.isEmpty())
    {
      options = null;
    }
    AttributeType type = modAttr.getAttributeType();
    AttrInfoWithOptions attrInfoWithOptions =  attributesInfo.get(type);
    AttrInfo attrInfo;
    if (attrInfoWithOptions != null)
    {
      attrInfo = attrInfoWithOptions.get(options);
    }
    else
    {
      attrInfo = null;
    }
    /*
     * The following code only works for multi-valued attributes.
@@ -260,7 +287,9 @@
    {
      attrInfo = new AttrInfo();
      if (attrInfoWithOptions == null)
      {
        attrInfoWithOptions = new AttrInfoWithOptions();
      }
      attrInfoWithOptions.put(options, attrInfo);
      attributesInfo.put(type, attrInfoWithOptions);
    }
@@ -268,9 +297,13 @@
    {
    case DELETE:
      if (modAttr.getValues().isEmpty())
      {
        attrInfo.delete(changeNumber);
      }
      else
      {
        attrInfo.delete(modAttr.getValues(), changeNumber);
      }
      break;
    case ADD:
@@ -319,10 +352,26 @@
        processLocalOrNonConflictModification(changeNumber, mod);
      }
      if (moreRecentChangenumber == null ||
          moreRecentChangenumber.older(changeNumber))
           moreRecentChangenumber.older(changeNumber))
      {
        moreRecentChangenumber = changeNumber;
      }
    }
    Attribute attr = encode();
    Modification mod;
    mod = new Modification(ModificationType.REPLACE, attr);
    mods.add(mod);
    modifiedEntry.removeAttribute(historicalAttrType);
    modifiedEntry.addAttribute(attr, null);
  }
  /**
   * Encode the historical information in an operational attribute.
   * @return The historical information encoded in an operational attribute.
   */
  public Attribute encode()
  {
    LinkedHashSet<AttributeValue> hist = new LinkedHashSet<AttributeValue>();
    for (Map.Entry<AttributeType, AttrInfoWithOptions> entryWithOptions :
@@ -330,7 +379,7 @@
    {
      AttributeType type = entryWithOptions.getKey();
      HashMap<Set<String> ,AttrInfo> attrwithoptions =
      HashMap<Set<String> , AttrInfo> attrwithoptions =
                                entryWithOptions.getValue().getAttributesInfo();
      for (Map.Entry<Set<String>, AttrInfo> entry : attrwithoptions.entrySet())
@@ -342,8 +391,15 @@
        ChangeNumber deleteTime = info.getDeleteTime();
        if (options != null)
        {
          StringBuilder optionsBuilder = new StringBuilder();
          for (String s : options)
            optionsString.concat(";"+s);
          {
            optionsBuilder.append(';');
            optionsBuilder.append(s);
          }
          optionsString = optionsBuilder.toString();
        }
        /* generate the historical information for deleted attributes */
        if (deleteTime != null)
@@ -391,7 +447,6 @@
          String strValue = type.getNormalizedPrimaryName()
              + optionsString + ":" + deleteTime.toString()
              + ":attrDel";
          delAttr = false;
          AttributeValue val = new AttributeValue(historicalAttrType, strValue);
          hist.add(val);
        }
@@ -399,16 +454,16 @@
    }
    Attribute attr;
    Modification mod;
    if (hist.isEmpty())
    {
      attr = new Attribute(historicalAttrType, HISTORICALATTRIBUTENAME, null);
    }
    else
    {
      attr = new Attribute(historicalAttrType, HISTORICALATTRIBUTENAME, hist);
    mod = new Modification(ModificationType.REPLACE, attr);
    mods.add(mod);
    modifiedEntry.removeAttribute(historicalAttrType);
    modifiedEntry.addAttribute(attr, null);
    }
    return attr;
  }
  /**
@@ -428,15 +483,22 @@
    if (ChangeNumber.compare(newChange, moreRecentChangenumber) <= 0)
    {
      if (info == null)
      {
        return false;   // the attribute was never modified -> no conflict
      else
      if (ChangeNumber.compare(newChange, info.getLastUpdateTime()) <= 0)
      }
      else if (ChangeNumber.compare(newChange, info.getLastUpdateTime()) <= 0)
      {
        return true; // the attribute was modified after this change -> conflict
      }
      else
      {
        return false;// the attribute was not modified more recently
      }
    }
    else
    {
      return false;
    }
  }
  /**
@@ -515,9 +577,13 @@
      modAttr.setValues(replValues);
      if (changeNumber.newer(attrInfo.getDeleteTime()))
      {
        attrInfo.setDeleteTime(changeNumber);
      }
      if (changeNumber.newer(attrInfo.getLastUpdateTime()))
      {
        attrInfo.setLastUpdateTime(changeNumber);
      }
    }
    else
    {
@@ -566,7 +632,9 @@
        }
      }
      if (changeNumber.newer(attrInfo.getLastUpdateTime()))
      {
        attrInfo.setLastUpdateTime(changeNumber);
      }
    }
    return true;
  }
@@ -668,10 +736,14 @@
      }
    }
    if (addValues.isEmpty())
    {
      modsIterator.remove();
    }
    if (changeNumber.newer(attrInfo.getLastUpdateTime()))
    {
      attrInfo.setLastUpdateTime(changeNumber);
    }
    return true;
  }
@@ -691,7 +763,9 @@
    AttrInfoWithOptions attrInfoWithOptions = null;
    if (hist == null)
    {
      return histObj;
    }
    for (Attribute attr : hist)
    {
@@ -734,8 +808,7 @@
        }
        else
        {
          attrType = lastAttrType;
          if (options != lastOptions)
          if (!options.equals(lastOptions))
          {
            attrInfo = new AttrInfo();
            attrInfoWithOptions.put(options, attrInfo);
@@ -782,6 +855,8 @@
    /* set the reference to the historical information in the entry */
    return histObj;
  }
  /**
   * Use this historical information to generate fake operations that would
   * result in this historical information.
opends/src/server/org/opends/server/util/LDIFReader.java
@@ -983,8 +983,8 @@
   *                                entry (used for writing reject information).
   * @param  line                   The line to decode.
   * @param  entryDN                The DN of the entry being decoded.
   * @param  attributeName          The name of the attribute to return
   *                                the values for.
   * @param  attributeName          The name and options of the attribute to
   *                                return the values for.
   *
   * @return                        The attribute in octet string form.
   * @throws  LDIFException         If a problem occurs while trying to decode
@@ -1007,13 +1007,17 @@
    String attrDescr = line.substring(0, colonPos);
    Attribute attribute = parseAttrDescription(attrDescr);
    String attrName = attribute.getName();
    String lowerName = toLowerCase(attrName);
    if(attributeName != null && !toLowerCase(attributeName).equals(lowerName))
    if (attributeName != null)
    {
      int msgID = MSGID_LDIF_INVALID_CHANGERECORD_ATTRIBUTE;
      String message = getMessage(msgID, lowerName, attributeName);
      throw new LDIFException(msgID, message, lastEntryLineNumber, false);
      Attribute expectedAttr = parseAttrDescription(attributeName);
      if (!attribute.equals(expectedAttr))
      {
        int msgID = MSGID_LDIF_INVALID_CHANGERECORD_ATTRIBUTE;
        String message = getMessage(msgID, attrDescr, attributeName);
        throw new LDIFException(msgID, message, lastEntryLineNumber, false);
      }
    }
    //  Now parse the attribute value.
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/HistoricalTest.java
New file
@@ -0,0 +1,166 @@
/*
 * 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.plugin;
import org.opends.server.synchronization.SynchronizationTestCase;
import org.opends.server.TestCaseUtils;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.tools.LDAPModify;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.Attribute;
import org.opends.server.core.DirectoryServer;
import org.testng.annotations.Test;
import org.testng.annotations.BeforeClass;
import static org.testng.Assert.assertEquals;
import java.util.List;
/**
 * Tests the Historical class.
 */
public class HistoricalTest
     extends SynchronizationTestCase
{
  /**
   * Set up synchronization on the test backend.
   * @throws Exception If an error occurs.
   */
  @BeforeClass
  @Override
  public void setUp()
       throws Exception
  {
    TestCaseUtils.initializeTestBackend(true);
    // Create an internal connection.
    connection = InternalClientConnection.getRootConnection();
    // Top level synchronization provider.
    String synchroStringDN = "cn=Synchronization Providers,cn=config";
    // Multimaster synchronization 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);
    // The synchronization server.
    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);
    // The suffix to be synchronized.
    String synchroServerStringDN = "o=test, " + synchroPluginStringDN;
    String synchroServerLdif = "dn: " + synchroServerStringDN + "\n"
         + "objectClass: top\n"
         + "objectClass: ds-cfg-synchronization-provider-config\n"
         + "cn: example\n"
         + "ds-cfg-synchronization-dn: o=test\n"
         + "ds-cfg-changelog-server: localhost:8989\n"
         + "ds-cfg-directory-server-id: 1\n"
         + "ds-cfg-receive-status: true\n";
    synchroServerEntry = TestCaseUtils.entryFromLdifString(synchroServerLdif);
    configureSynchronization();
  }
  /**
   * Tests that the attribute modification history is correctly read from
   * and written to an operational attribute of the entry.
   * @throws Exception If the test fails.
   */
  @Test
  public void testEncoding()
       throws Exception
  {
    //  Add a test entry.
    TestCaseUtils.addEntry(
         "dn: uid=user.1,o=test",
         "objectClass: top",
         "objectClass: person",
         "objectClass: organizationalPerson",
         "objectClass: inetOrgPerson",
         "uid: user.1",
         "cn: Aaccf Amar",
         "sn: Amar",
         "givenName: Aaccf",
         "userPassword: password",
         "description: Initial description"
       );
    // Modify the test entry to give it some history.
    String path = TestCaseUtils.createTempFile(
         "dn: uid=user.1,o=test",
         "changetype: modify",
         "add: cn;lang-en",
         "cn;lang-en: Aaccf Amar",
         "cn;lang-en: Aaccf A Amar",
         "-",
         "replace: description",
         "description: replaced description",
         "-"
    );
    String[] args =
    {
      "-h", "127.0.0.1",
      "-p", String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-D", "cn=Directory Manager",
      "-w", "password",
      "-f", path
    };
    assertEquals(LDAPModify.mainModify(args, false, null, System.err), 0);
    // Read the entry back to get its history operational attribute.
    DN dn = DN.decode("uid=user.1,o=test");
    Entry entry = DirectoryServer.getEntry(dn);
    // Check that encoding and decoding preserves the history information.
    Historical hist = Historical.load(entry);
    Attribute after = hist.encode();
    List<Attribute> attrs = entry.getAttribute(Historical.historicalAttrType);
    Attribute before = attrs.get(0);
    assertEquals(after, before);
  }
}
opends/tests/unit-tests-testng/src/server/org/opends/server/synchronization/plugin/ModifyConflictTest.java
@@ -45,9 +45,6 @@
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.plugin.FakeOperation;
import org.opends.server.synchronization.plugin.FakeOperationComparator;
import org.opends.server.synchronization.plugin.Historical;
import org.opends.server.synchronization.protocol.ModifyContext;
import org.opends.server.synchronization.protocol.SynchronizationMessage;
import org.opends.server.synchronization.protocol.UpdateMessage;
@@ -132,7 +129,7 @@
     * sure...)
     */
    testModify(entry, hist, "description", ModificationType.ADD,
        "older value", 2, false);;
        "older value", 2, false);
    /*
     * Now simulate an add at a later date that the previous replace.
@@ -387,7 +384,7 @@
        InternalClientConnection.getRootConnection();
    ChangeNumber t = new ChangeNumber(date, (short) 0, (short) 0);
    /* create AttributeType description that will be usedfor this test */
    /* create AttributeType description that will be used for this test */
    AttributeType attrType =
      DirectoryServer.getAttributeType(attrName, true);
@@ -405,7 +402,7 @@
    modOp.setAttachment(SYNCHROCONTEXT, ctx);
    hist.replayOperation(modOp, entry);
    if (modType.intValue() == ModificationType.ADD.intValue())
    if (modType == ModificationType.ADD)
    {
      AddOperation addOp = new AddOperation(connection, 1, 1, null, entry
          .getDN(), entry.getObjectClasses(), entry.getUserAttributes(),