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

mrossign
31.15.2008 4c7d89a5b8f1503bf63988ae13d47d269e1615de
Partial fix for #3525: Attribute modifications are not replicated for modDN operations
Warning: ModifyDNMsg PDU is updated and modifications are exchanged through the replication network.
However at replay time, the moddn operation is re-created with the modifications, but they cannot be replayed at this time:
The comment of the addModification() method for ModifyDNOperation states that "This should only be called by pre-operation plugins"

So either some modifs are to be done on core side, or we may put the modifications in the operaton context and intercept the replay
at the pre-op stage and effectively replay the modifications at this time.

...to be discussed.
1 files added
5 files modified
403 ■■■■ changed files
opends/src/server/org/opends/server/replication/protocol/ModifyCommonMsg.java 137 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ModifyDNMsg.java 90 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java 55 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java 2 ●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/ProtocolCompatibilityTest.java 55 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java 64 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/replication/protocol/ModifyCommonMsg.java
New file
@@ -0,0 +1,137 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.replication.protocol;
import java.util.ArrayList;
import java.util.List;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.plugin.Historical;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeUsage;
import org.opends.server.types.Modification;
/**
 * This class holds every common code for the modify messages (mod, moddn).
 */
public abstract class ModifyCommonMsg extends UpdateMsg {
  /**
   * The modifications kept encoded in the message.
   */
  protected byte[] encodedMods = new byte[0];
  /**
   * Creates a new ModifyCommonMsg.
   */
  public ModifyCommonMsg()
  {
    super();
  }
  /**
   * Creates a new ModifyCommonMsg with the given informations.
   *
   * @param ctx The replication Context of the operation for which the
   *            update message must be created,.
   * @param dn The DN of the entry on which the change
   *           that caused the creation of this object happened
   */
  public ModifyCommonMsg(OperationContext ctx, String dn)
  {
   super(ctx, dn);
  }
  /**
   * Creates a new ModifyCommonMsg with the given informations.
   *
   * @param cn        The ChangeNumber of the operation for which the
   *                  UpdateMessage is created.
   * @param entryUUID The Unique identifier of the entry that is updated
   *                  by the operation for which the UpdateMessage is created.
   * @param dn        The DN of the entry on which the change
   *                  that caused the creation of this object happened
   */
  public ModifyCommonMsg(ChangeNumber cn, String entryUUID, String dn)
  {
    super(cn, entryUUID, dn);
  }
  /**
   * Set the Modification associated to the UpdateMsg to the provided value.
   *
   * @param mods The new Modification associated to this ModifyMsg.
   */
  public void setMods(List<Modification> mods)
  {
    encodedMods = modsToByte(mods);
  }
  /**
   * Encode an ArrayList of Modification into a byte[] suitable
   * for storage in a database or send on the network.
   *
   * @param mods the ArrayList of Modification to be encoded.
   * @return The encoded modifications.
   */
  protected byte[] modsToByte(List<Modification> mods)
  {
    if ((mods == null) || (mods.size() == 0))
      return new byte[0];
    ArrayList<ASN1Element> modsASN1;
    modsASN1 = new ArrayList<ASN1Element>(mods.size());
    for (Modification mod : mods)
    {
      Attribute attr = mod.getAttribute();
      AttributeType type = attr.getAttributeType();
      if (type != null )
      {
        if (AttributeUsage.DSA_OPERATION.equals(type.getUsage()))
        {
          // Attributes with a dsaOperation usage should not be synchronized.
          // skip them.
          continue;
        }
      }
      if (!Historical.isHistoricalAttribute(attr))
      {
        LDAPModification ldapmod = new LDAPModification(
          mod.getModificationType(), new LDAPAttribute(mod.getAttribute()));
        modsASN1.add(ldapmod.encode());
      }
    }
    return ASN1Element.encodeValue(modsASN1);
  }
}
opends/src/server/org/opends/server/replication/protocol/ModifyDNMsg.java
@@ -29,22 +29,29 @@
import static org.opends.server.replication.protocol.OperationContext.*;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.DataFormatException;
import org.opends.server.core.ModifyDNOperationBasis;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.DN;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.LDAPException;
import org.opends.server.types.Modification;
import org.opends.server.types.RDN;
import org.opends.server.types.operation.PostOperationModifyDNOperation;
/**
 * Message used to send Modify DN information.
 */
public class ModifyDNMsg extends UpdateMsg
public class ModifyDNMsg extends ModifyCommonMsg
{
  private String newRDN;
  private String newSuperior;
@@ -61,6 +68,8 @@
    super((OperationContext) operation.getAttachment(SYNCHROCONTEXT),
        operation.getRawEntryDN().stringValue());
    encodedMods = modsToByte(operation.getModifications());
    ModifyDnContext ctx =
      (ModifyDnContext) operation.getAttachment(SYNCHROCONTEXT);
    newSuperiorId = ctx.getNewParentId();
@@ -74,17 +83,19 @@
  }
  /**
   * construct a new Modify DN message.
   * Construct a new Modify DN message (no mods).
   * Note: Keep this constructor version to support already written tests, not
   * using mods.
   *
   * @param dn The dn to use for building the message.
   * @param changeNumber The changeNumberto use for building the message.
   * @param uid The unique id to use for building the message.
   * @param uid          The unique id to use for building the message.
   * @param newParentUid The new parent unique id to use for building
   *                     the message.
   * @param deleteOldRdn boolean indicating if old rdn must be deleted to use
   *                     for building the message.
   * @param newSuperior The new Superior entry to use for building the message.
   * @param newRDN The new Rdn to use for building the message.
   * @param newSuperior  The new Superior entry to use for building the message.
   * @param newRDN       The new Rdn to use for building the message.
   */
  public ModifyDNMsg(String dn, ChangeNumber changeNumber, String uid,
                     String newParentUid, boolean deleteOldRdn,
@@ -100,6 +111,29 @@
  }
  /**
   * Construct a new Modify DN message (with mods).
   *
   * @param dn The dn to use for building the message.
   * @param changeNumber The changeNumberto use for building the message.
   * @param uid The unique id to use for building the message.
   * @param newParentUid The new parent unique id to use for building
   *                     the message.
   * @param deleteOldRdn boolean indicating if old rdn must be deleted to use
   *                     for building the message.
   * @param newSuperior  The new Superior entry to use for building the message.
   * @param newRDN       The new Rdn to use for building the message.
   * @param mods         The mod of the operation.
   */
  public ModifyDNMsg(String dn, ChangeNumber changeNumber, String uid,
                     String newParentUid, boolean deleteOldRdn,
                     String newSuperior, String newRDN, List<Modification> mods)
  {
    this(dn, changeNumber, uid, newParentUid, deleteOldRdn, newSuperior,
      newRDN);
    this.encodedMods = modsToByte(mods);
  }
  /**
   * Creates a new ModifyDN message from a byte[].
   *
   * @param in The byte[] from which the operation must be read.
@@ -145,6 +179,33 @@
      deleteOldRdn = false;
    else
      deleteOldRdn = true;
    pos++;
    // For easiness (no additional method), simply compare PDU type to
    // know if we have to read the mods of V2
    if (in[0] == MSG_TYPE_MODIFYDN)
    {
      /* Read the mods : all the remaining bytes but the terminating 0 */
      length = in.length - pos - 1;
      if (length > 0) // Otherwise, there is only the trailing 0 byte which we
        // do not need to read
      {
        encodedMods = new byte[length];
        try
        {
          System.arraycopy(in, pos, encodedMods, 0, length);
        } catch (IndexOutOfBoundsException e)
        {
          throw new DataFormatException(e.getMessage());
        } catch (ArrayStoreException e)
        {
          throw new DataFormatException(e.getMessage());
        } catch (NullPointerException e)
        {
          throw new DataFormatException(e.getMessage());
        }
      }
    }
  }
  /**
@@ -153,6 +214,7 @@
  @Override
  public AbstractOperation createOperation(
         InternalClientConnection connection, String newDn)
         throws LDAPException, ASN1Exception
  {
    ModifyDNOperationBasis moddn =  new ModifyDNOperationBasis(connection,
               InternalClientConnection.nextOperationID(),
@@ -160,6 +222,11 @@
               new ASN1OctetString(newDn), new ASN1OctetString(newRDN),
               deleteOldRdn,
               (newSuperior == null ? null : new ASN1OctetString(newSuperior)));
    ArrayList<ASN1Element> mods = ASN1Element.decodeElements(encodedMods);
    for (ASN1Element elem : mods)
      moddn.addModification(LDAPModification.decode(elem).toModification());
    ModifyDnContext ctx = new ModifyDnContext(getChangeNumber(), getUniqueId(),
                                              newSuperiorId);
    moddn.setAttachment(SYNCHROCONTEXT, ctx);
@@ -194,6 +261,8 @@
    else
      length += 1;
    length += encodedMods.length + 1;
    byte[] resultByteArray = encodeHeader(MSG_TYPE_MODIFYDN, length);
    int pos = resultByteArray.length - length;
@@ -222,6 +291,15 @@
    else
      resultByteArray[pos++] = 0;
    /* add the mods */
    if (encodedMods.length > 0)
    {
      pos = resultByteArray.length - (encodedMods.length + 1);
      addByteArray(encodedMods, resultByteArray, pos);
    }
    else
      resultByteArray[pos++] = 0;
    return resultByteArray;
  }
@@ -438,7 +516,7 @@
  {
    // The MODDN message size are mainly dependent on the
    // size of the DN. let's assume that they average on 100 bytes
    return 100;
    return encodedMods.length + 100;
  }
  /**
opends/src/server/org/opends/server/replication/protocol/ModifyMsg.java
@@ -31,16 +31,11 @@
import org.opends.server.core.ModifyOperationBasis;
import org.opends.server.protocols.asn1.ASN1Exception;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.LDAPAttribute;
import org.opends.server.protocols.ldap.LDAPModification;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.replication.common.ChangeNumber;
import org.opends.server.replication.plugin.Historical;
import org.opends.server.types.AbstractOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.AttributeType;
import org.opends.server.types.AttributeUsage;
import org.opends.server.types.DN;
import org.opends.server.types.LDAPException;
import org.opends.server.types.Modification;
@@ -56,10 +51,8 @@
/**
 * Message used to send Modify information.
 */
public class ModifyMsg extends UpdateMsg
public class ModifyMsg extends ModifyCommonMsg
{
  private byte[] encodedMods = null;
  /**
   * Creates a new Modify message from a ModifyOperation.
   *
@@ -170,42 +163,6 @@
  }
  /**
   * Encode an ArrayList of Modification into a byte[] suitable
   * for storage in a database or send on the network.
   *
   * @param mods the ArrayList of Modification to be encoded.
   */
  private byte[] modsToByte(List<Modification> mods)
  {
    ArrayList<ASN1Element> modsASN1;
    modsASN1 = new ArrayList<ASN1Element>(mods.size());
    for (Modification mod : mods)
    {
      Attribute attr = mod.getAttribute();
      AttributeType type = attr.getAttributeType();
      if (type != null )
      {
        if (AttributeUsage.DSA_OPERATION.equals(type.getUsage()))
        {
          // Attributes with a dsaOperation usage should not be synchronized.
          // skip them.
          continue;
        }
      }
      if (!Historical.isHistoricalAttribute(attr))
      {
        LDAPModification ldapmod = new LDAPModification(
          mod.getModificationType(), new LDAPAttribute(mod.getAttribute()));
        modsASN1.add(ldapmod.encode());
      }
    }
    return ASN1Element.encodeValue(modsASN1);
  }
  /**
   * {@inheritDoc}
   */
  @Override
@@ -235,16 +192,6 @@
  }
  /**
   * Set the Modification associated to the UpdateMsg to the provided value.
   *
   * @param mods The new Modification associated to this ModifyMsg.
   */
  public void setMods(List<Modification> mods)
  {
    encodedMods = modsToByte(mods);
  }
  /**
   * {@inheritDoc}
   */
  @Override
opends/src/server/org/opends/server/replication/protocol/ReplServerStartMsg.java
@@ -156,7 +156,7 @@
      sslEncryption = Boolean.valueOf(new String(in, pos, length, "UTF-8"));
      pos += length +1;
      // For easiness (no additional method), simpy compare PDU type to
      // For easiness (no additional method), simply compare PDU type to
      // know if we have to read new parameters of V2
      if (in[0] == MSG_TYPE_REPL_SERVER_START)
      {
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/ProtocolCompatibilityTest.java
@@ -55,6 +55,7 @@
import static org.opends.server.replication.protocol.OperationContext.SYNCHROCONTEXT;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
/**
 * Test the conversions between the various protocol versions.
@@ -505,12 +506,44 @@
  
  @DataProvider(name = "createModifyDnData")
  public Object[][] createModifyDnData() {
    AttributeType type = DirectoryServer.getAttributeType("description");
    Attribute attr1 = Attributes.create("description", "new value");
    Modification mod1 = new Modification(ModificationType.REPLACE, attr1);
    List<Modification> mods1 = new ArrayList<Modification>();
    mods1.add(mod1);
    Attribute attr2 = Attributes.empty("description");
    Modification mod2 = new Modification(ModificationType.DELETE, attr2);
    List<Modification> mods2 = new ArrayList<Modification>();
    mods2.add(mod1);
    mods2.add(mod2);
    AttributeBuilder builder = new AttributeBuilder(type);
    List<Modification> mods3 = new ArrayList<Modification>();
    builder.add("string");
    builder.add("value");
    builder.add("again");
    Attribute attr3 = builder.toAttribute();
    Modification mod3 = new Modification(ModificationType.ADD, attr3);
    mods3.add(mod3);
    List<Modification> mods4 = new ArrayList<Modification>();
    for (int i = 0; i < 10; i++)
    {
      Attribute attr = Attributes.create("description", "string"
          + String.valueOf(i));
      Modification mod = new Modification(ModificationType.ADD, attr);
      mods4.add(mod);
    }
    return new Object[][] {
        {"dc=test,dc=com", "dc=new", "11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222", false, "dc=change", false, AssuredMode.SAFE_DATA_MODE, (byte)0},
        {"dc=test,dc=com", "dc=new", "33333333-3333-3333-3333-333333333333", "44444444-4444-4444-4444-444444444444", true, "dc=change", true, AssuredMode.SAFE_READ_MODE, (byte)1},
        {"dc=test,dc=com", "dc=new", "55555555-5555-5555-5555-555555555555", "66666666-6666-6666-6666-666666666666", false, null, true, AssuredMode.SAFE_READ_MODE, (byte)3},
        {"dc=test,dc=com", "dc=new", "11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222", false, "dc=change", mods1, false, AssuredMode.SAFE_DATA_MODE, (byte)0},
        {"dc=test,dc=com", "dc=new", "33333333-3333-3333-3333-333333333333", "44444444-4444-4444-4444-444444444444", true, "dc=change", mods2, true, AssuredMode.SAFE_READ_MODE, (byte)1},
        {"dc=test,dc=com", "dc=new", "55555555-5555-5555-5555-555555555555", "66666666-6666-6666-6666-666666666666", false, null, mods3, true, AssuredMode.SAFE_READ_MODE, (byte)3},
        {"dc=delete,dc=an,dc=entry,dc=with,dc=a,dc=long dn",
                   "dc=new", "77777777-7777-7777-7777-777777777777", "88888888-8888-8888-8888-888888888888",true, null, true, AssuredMode.SAFE_DATA_MODE, (byte)99},
                   "dc=new", "77777777-7777-7777-7777-777777777777", "88888888-8888-8888-8888-888888888888",true, null, mods4, true, AssuredMode.SAFE_DATA_MODE, (byte)99},
        };
  }
@@ -519,10 +552,10 @@
   * using protocol V1 and V2 are working.
   */
  @Test(dataProvider = "createModifyDnData")
  public void modifyDnTest(String rawDN, String newRdn, String uid, String newParentUid,
  public void modifyDnMsgTest(String rawDN, String newRdn, String uid, String newParentUid,
                                   boolean deleteOldRdn, String newSuperior,
                                   boolean isAssured, AssuredMode assuredMode,
                                   byte safeDataLevel)
                                   List<Modification> mods, boolean isAssured,
                                   AssuredMode assuredMode, byte safeDataLevel)
         throws Exception
  {
    // Create V2 message
@@ -530,7 +563,7 @@
                                      (short) 596, (short) 13);
    ModifyDNMsg msg = new ModifyDNMsg(rawDN, cn, uid,
                     newParentUid, deleteOldRdn,
                     newSuperior, newRdn);
                     newSuperior, newRdn, mods);
    msg.setAssured(isAssured);
    msg.setAssuredMode(assuredMode);
@@ -558,7 +591,7 @@
    assertEquals(newMsg.getNewSuperiorId(), msg.getNewSuperiorId());
    assertEquals(newMsg.deleteOldRdn(), msg.deleteOldRdn());
    //        Create a modDn operation from each message to compare mods (kept encoded in messages)
    //        Create a modDn operation from each message to compare fields)
    Operation op = msg.createOperation(connection);
    Operation generatedOperation = newMsg.createOperation(connection);
@@ -570,15 +603,17 @@
    assertEquals(modDnOpBasis.getRawEntryDN(), genModDnOpBasis.getRawEntryDN());
    assertEquals( modDnOpBasis.getAttachment(SYNCHROCONTEXT),
                  genModDnOpBasis.getAttachment(SYNCHROCONTEXT));
    assertEquals(modDnOpBasis.getModifications(), genModDnOpBasis.getModifications());
    // Check default value for only V2 fields
    assertEquals(newMsg.getAssuredMode(), AssuredMode.SAFE_DATA_MODE);
    assertEquals(newMsg.getSafeDataLevel(), (byte)-1);
    assertEquals(modDnOpBasis.getModifications(), mods);
    assertTrue(genModDnOpBasis.getModifications() == null);
    // Set again only V2 fields
    newMsg.setAssuredMode(assuredMode);
    newMsg.setSafeDataLevel(safeDataLevel);
    newMsg.setMods(mods);
    // Serialize in V2 msg
    ModifyDNMsg v2Msg = (ModifyDNMsg)ReplicationMsg.generateMsg(newMsg.getBytes());
opends/tests/unit-tests-testng/src/server/org/opends/server/replication/protocol/SynchronizationMsgTest.java
@@ -318,21 +318,54 @@
  @DataProvider(name = "createModifyDnData")
  public Object[][] createModifyDnData() {
    AttributeType type = DirectoryServer.getAttributeType("description");
    Attribute attr1 = Attributes.create("description", "new value");
    Modification mod1 = new Modification(ModificationType.REPLACE, attr1);
    List<Modification> mods1 = new ArrayList<Modification>();
    mods1.add(mod1);
    Attribute attr2 = Attributes.empty("description");
    Modification mod2 = new Modification(ModificationType.DELETE, attr2);
    List<Modification> mods2 = new ArrayList<Modification>();
    mods2.add(mod1);
    mods2.add(mod2);
    AttributeBuilder builder = new AttributeBuilder(type);
    List<Modification> mods3 = new ArrayList<Modification>();
    builder.add("string");
    builder.add("value");
    builder.add("again");
    Attribute attr3 = builder.toAttribute();
    Modification mod3 = new Modification(ModificationType.ADD, attr3);
    mods3.add(mod3);
    List<Modification> mods4 = new ArrayList<Modification>();
    for (int i = 0; i < 10; i++)
    {
      Attribute attr = Attributes.create("description", "string"
          + String.valueOf(i));
      Modification mod = new Modification(ModificationType.ADD, attr);
      mods4.add(mod);
    }
    return new Object[][] {
        {"dc=test,dc=com", "dc=new", false, "dc=change", false, AssuredMode.SAFE_DATA_MODE, (byte)0},
        {"dc=test,dc=com", "dc=new", true, "dc=change", true, AssuredMode.SAFE_READ_MODE, (byte)1},
        {"dc=test,dc=com", "dc=new", false, "dc=change", mods1, false, AssuredMode.SAFE_DATA_MODE, (byte)0},
        {"dc=test,dc=com", "dc=new", true, "dc=change", mods2, true, AssuredMode.SAFE_READ_MODE, (byte)1},
        // testNG does not like null argument so use "" for the newSuperior
        // instead of null
        {"dc=test,dc=com", "dc=new", false, "", true, AssuredMode.SAFE_READ_MODE, (byte)3},
        {"dc=test,dc=com", "dc=new", false, "", mods3, true, AssuredMode.SAFE_READ_MODE, (byte)3},
        {"dc=delete,dc=an,dc=entry,dc=with,dc=a,dc=long dn",
                   "dc=new",true, "", true, AssuredMode.SAFE_DATA_MODE, (byte)99},
                   "dc=new", true, "", mods4, true, AssuredMode.SAFE_DATA_MODE, (byte)99},
        };
  }
  @Test(dataProvider = "createModifyDnData")
  public void modifyDnTest(String rawDN, String newRdn,
  public void modifyDnMsgTest(String rawDN, String newRdn,
                                   boolean deleteOldRdn, String newSuperior,
                                   boolean isAssured, AssuredMode assuredMode,
                                   List<Modification> mods, boolean isAssured,
                                   AssuredMode assuredMode,
                               byte safeDataLevel)
         throws Exception
  {
@@ -349,6 +382,8 @@
        new ModifyDnContext(cn, "uniqueid", "newparentId"));
    LocalBackendModifyDNOperation localOp =
      new LocalBackendModifyDNOperation(op);
    for (Modification mod : mods)
      localOp.addModification(mod);
    ModifyDNMsg msg = new ModifyDNMsg(localOp);
    msg.setAssured(isAssured);
@@ -363,14 +398,21 @@
    assertEquals(generatedMsg.getAssuredMode(), assuredMode);
    assertEquals(generatedMsg.getSafeDataLevel(), safeDataLevel);
    Operation oriOp = msg.createOperation(connection);
    Operation generatedOperation = generatedMsg.createOperation(connection);
    ModifyDNOperationBasis mod2 = (ModifyDNOperationBasis) generatedOperation;
    assertEquals(oriOp.getClass(), ModifyDNOperationBasis.class);
    assertEquals(generatedOperation.getClass(), ModifyDNOperationBasis.class);
    ModifyDNOperationBasis moddn1 = (ModifyDNOperationBasis) oriOp;
    ModifyDNOperationBasis moddn2 = (ModifyDNOperationBasis) generatedOperation;
    assertEquals(msg.getChangeNumber(), generatedMsg.getChangeNumber());
    assertEquals(op.getRawEntryDN(), mod2.getRawEntryDN());
    assertEquals(op.getRawNewRDN(), mod2.getRawNewRDN());
    assertEquals(op.deleteOldRDN(), mod2.deleteOldRDN());
    assertEquals(op.getRawNewSuperior(), mod2.getRawNewSuperior());
    assertEquals(moddn1.getRawEntryDN(), moddn2.getRawEntryDN());
    assertEquals(moddn1.getRawNewRDN(), moddn2.getRawNewRDN());
    assertEquals(moddn1.deleteOldRDN(), moddn2.deleteOldRDN());
    assertEquals(moddn1.getRawNewSuperior(), moddn2.getRawNewSuperior());
    assertEquals(moddn1.getModifications(), moddn2.getModifications());
    // Create an update message from this op
    ModifyDNMsg updateMsg = (ModifyDNMsg) UpdateMsg.generateMsg(localOp);