/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2011-2013 ForgeRock AS */ package org.opends.server.replication.plugin; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import org.testng.annotations.Test; import static org.testng.Assert.*; import static org.opends.server.replication.protocol.OperationContext.*; import org.opends.server.core.AddOperationBasis; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyOperationBasis; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.replication.ReplicationTestCase; import org.opends.server.replication.common.ChangeNumber; import org.opends.server.replication.protocol.ModifyContext; import org.opends.server.replication.protocol.ReplicationMsg; import org.opends.server.replication.protocol.LDAPUpdateMsg; import org.opends.server.types.*; import org.opends.server.workflowelement.localbackend.LocalBackendAddOperation; import org.opends.server.workflowelement.localbackend.LocalBackendModifyOperation; import static org.opends.server.TestCaseUtils.*; /* * Test the conflict resolution for modify operations As a consequence, * this will also test the Historical.java Class This is still a work in * progress. currently implemented tests - check that an replace with a * smaller csn is ignored should test : - conflict with multi-valued * attributes - conflict with single-valued attributes - conflict with * options - conflict with binary attributes - Replace, add, delete * attribute, delete attribute value - conflict on the objectclass * attribute. * Added tests for multiple mods on same attribute in the same modify operation. */ public class ModifyConflictTest extends ReplicationTestCase { private static final String ORGANIZATION = "organization"; private static final String DISPLAYNAME = "displayname"; private static final String EMPLOYEENUMBER = "employeenumber"; private static final String DESCRIPTION = "description"; private static final String SYNCHIST = "ds-sync-hist"; /** * Test that conflict between a modify-replace and modify-add for * multi-valued attributes are handled correctly. */ @Test() public void replaceAndAdd() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:repl:init value"); Attribute repl = builder.toAttribute(); /* * simulate a modify-replace done at time t10 */ testModify(entry, hist, DESCRIPTION, ModificationType.REPLACE, "init value", 10, true); assertEquals(hist.encodeAndPurge(), repl); /* * Now simulate an add at an earlier date that the previous replace * conflict resolution should remove it. */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "older value", 1, false); assertEquals(hist.encodeAndPurge(), repl); /* * Now simulate an add at an earlier date that the previous replace * conflict resolution should remove it. (a second time to make * sure...) */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "older value", 2, false); assertEquals(hist.encodeAndPurge(), repl); /* * Now simulate an add at a later date that the previous replace. * conflict resolution should keep it */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "new value", 11, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:repl:init value"); builder.add(DESCRIPTION + ":000000000000000b000000000000:add:new value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between a modify-replace and modify-add for * single-valued attributes are handled correctly. */ @Test() public void replaceAndAddSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":000000000000000a000000000000:repl:init value"); Attribute repl = builder.toAttribute(); /* * simulate a modify-replace done at time t10 */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "init value", 10, true); assertEquals(hist.encodeAndPurge(), repl); /* * Now simulate an add at an earlier date that the previous replace * conflict resolution should remove it. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "older value", 1, false); assertEquals(hist.encodeAndPurge(), repl); /* * Now simulate an add at an earlier date that the previous replace * conflict resolution should remove it. (a second time to make * sure...) */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "older value", 2, false); assertEquals(hist.encodeAndPurge(), repl); /* * Now simulate an add at a later date that the previous replace. * conflict resolution should remove it */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "new value", 11, false); assertEquals(hist.encodeAndPurge(), repl); List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); Attribute attr = attrs.get(0); assertEquals(1, attr.size()); attr.contains(AttributeValues.create(attr.getAttributeType(), "init value")); } /** * Test that replace with null value is correctly seen as a delete * by doing first a replace with null, then add at at previous time * then check that the add was ignored */ @Test() public void replaceWithNull() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000003000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * simulate a replace with null done at time t3 */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, null, 3, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at an earlier date that the previous replace. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "older value", 1, false); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at an earlier date that the previous delete. The * conflict resolution should detect that this add must be ignored. (a * second time to make sure that historical information is kept...) */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "older value", 2, false); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at a later date that the previous delete. * conflict resolution should keep it */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "new value", 4, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000004000000000000:add:new value"); builder.add(DISPLAYNAME + ":0000000000000003000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between modify-add and modify-replace for * multi-valued attributes are handled correctly. */ @Test() public void addAndReplace() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a modify-add done at time t10 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "init value", 10, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a replace at an earlier date that the previous replace * conflict resolution should keep it. */ testModify(entry, hist, DESCRIPTION, ModificationType.REPLACE, "older value", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); builder.add(DESCRIPTION + ":0000000000000001000000000000:repl:older value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a replace at an earlier date that the previous replace * conflict resolution should remove it. (a second time to make * sure...) */ testModify(entry, hist, DESCRIPTION, ModificationType.REPLACE, "older value", 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); builder.add(DESCRIPTION + ":0000000000000002000000000000:repl:older value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a replace at a later date that the previous replace. * conflict resolution should keep it */ testModify(entry, hist, DESCRIPTION, ModificationType.REPLACE, "new value", 11, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000b000000000000:repl:new value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between modify-add and modify-replace for * single-valued attributes are handled correctly. */ @Test() public void addAndReplaceSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a modify-add done at time 2 */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "init value", 2, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000002000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a replace at an earlier date that the previous replace * conflict resolution should keep it. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "older value", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:repl:older value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); Attribute attr = attrs.get(0); assertEquals(1, attr.size()); attr.contains(AttributeValues.create(attr.getAttributeType(), "older value")); /* * Now simulate a replace at a later date. * Conflict resolution should keept it. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "newer value", 3, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000003000000000000:repl:newer value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); attrs = entry.getAttribute(DISPLAYNAME); attr = attrs.get(0); assertEquals(1, attr.size()); attr.contains(AttributeValues.create(attr.getAttributeType(), "newer value")); } /** * Test that conflict between a modify-delete-attribute and modify-add * for multi-valued attributes are handled correctly. */ @Test() public void deleteAndAdd() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * simulate a delete of the whole description attribute done at time * t10 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, null, 10, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at an earlier date that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "older value", 1, false); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at an earlier date that the previous delete. The * conflict resolution should detect that this add must be ignored. (a * second time to make sure that historical information is kept...) */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "older value", 2, false); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at a later date that the previous delete. * conflict resolution should keep it */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "new value", 11, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000b000000000000:add:new value"); builder.add(DESCRIPTION + ":000000000000000a000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between a delete-attribute value and * add attribute-values from 2 different servers are handled correctly. * This test was created to reproduce issue 3392. */ @Test() public void delValueAndAddValue() throws Exception { // create an entry to use with conflicts tests. Entry entry = initializeEntry(); // // Create description with values value1 and value2 and add // this attribute to the entry. // AttributeBuilder builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); entry.addAttribute(builder.toAttribute(), duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a delete of the description attribute value "value1" * done at time t1 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value1", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add of "value3" at time t2 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value3", 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value3"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a delete of value "value1" at time t3 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value1", 3, false); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value3"); builder.add(DESCRIPTION + ":0000000000000003000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add of "value4" at time t4 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value4", 4, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value3"); builder.add(DESCRIPTION + ":0000000000000003000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000004000000000000:add:value4"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between several delete-attribute value and * are handled correctly. * This test was created to reproduce and fix issue 3397. */ @Test() public void delValueAndDelValue() throws Exception { // create an entry to use with conflicts tests. Entry entry = initializeEntry(); // // Create an attribute with values value1, value2, value3 and value4 and // add this attribute to the entry. // AttributeBuilder builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); builder.add("value3"); builder.add("value4"); List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); entry.addAttribute(builder.toAttribute(), duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a delete of the description attribute values * "value1" and "value2" done at time t1 */ testModify(entry, hist, DESCRIPTION, buildModWith2Vals(DESCRIPTION, ModificationType.DELETE, "value1", "value2"), 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value2"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of the description attribute values * "value2" and "value3" done at time t1 */ testModify(entry, hist, DESCRIPTION, buildModWith2Vals(DESCRIPTION, ModificationType.DELETE, "value2", "value3"), 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value2"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value3"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); // Check that entry now only contains 1 attribute value : "value1" List<Attribute> attrs = entry.getAttribute(DESCRIPTION); Attribute attr = attrs.get(0); assertEquals(1, attr.size()); } /** * Test that conflict between a delete attribute and a delete * value on a single valued attribute works correctly. * This test was created to reproduce and fix issue 3399. */ @Test() public void delAttributeAndDelValueSingle() throws Exception { // create an entry to use with conflicts tests. Entry entry = initializeEntry(); // // Create a single valued attribute with value : "value1" // add this attribute to the entry. // List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); Attribute attribute = Attributes.create(EMPLOYEENUMBER, "value1"); entry.addAttribute(attribute, duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a delete of attribute employeenumber. */ testModify( entry, hist, EMPLOYEENUMBER, ModificationType.DELETE, null, 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(EMPLOYEENUMBER + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * now simulate a delete of value "value1" */ testModify( entry, hist, EMPLOYEENUMBER, ModificationType.DELETE, "value1", 2, false); builder = new AttributeBuilder(SYNCHIST); builder.add(EMPLOYEENUMBER + ":0000000000000002000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between a delete attribute and a delete * value on a single valued attribute works correctly. * This test was created to reproduce and fix issue 3399. */ @Test() public void delValueAndDelAttributeSingle() throws Exception { // create an entry to use with conflicts tests. Entry entry = initializeEntry(); // Create a single valued attribute with value : "value1" // add this attribute to the entry. List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); Attribute attribute = Attributes.create(EMPLOYEENUMBER, "value1"); entry.addAttribute(attribute, duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * now simulate a delete of value "value1" */ testModify( entry, hist, EMPLOYEENUMBER, ModificationType.DELETE, "value1", 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(EMPLOYEENUMBER + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of attribute employeenumber. */ testModify( entry, hist, EMPLOYEENUMBER, ModificationType.DELETE, null, 2, false); builder = new AttributeBuilder(SYNCHIST); builder.add(EMPLOYEENUMBER + ":0000000000000002000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between a delete-attribute value and * add attribute-values from 2 different servers * and replayed in the non-natural order are handled correctly. * This test was created to reproduce issue 3392. */ @Test() public void delValueAndAddValueDisordered() throws Exception { // create an entry to use with conflicts tests. Entry entry = initializeEntry(); // // Create description with values value1 and value2 and add // this attribute to the entry. // AttributeBuilder builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); entry.addAttribute(builder.toAttribute(), duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a delete of the description attribute value "value1" * done at time t3 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value1", 3, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000003000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add of "value3" at time t4 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value3", 4, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000003000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000004000000000000:add:value3"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a delete of value "value1" at time t1 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value1", 1, false); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000003000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000004000000000000:add:value3"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add of "value4" at time t2 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value4", 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000003000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000004000000000000:add:value3"); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value4"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between a modify-delete-attribute and modify-add * for multi-valued attributes are handled correctly. */ @Test() public void deleteAndAddSingle() throws Exception { Entry entry = initializeEntry(); // Create a single valued attribute with value : "value1" // add this attribute to the entry. List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); Attribute attribute = Attributes.create(DISPLAYNAME, "value1"); entry.addAttribute(attribute, duplicateValues); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000003000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a delete of the whole description attribute done at time * t2 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 3, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at an earlier date that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "older value", 1, false); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at an earlier date that the previous delete. The * conflict resolution should detect that this add must be ignored. (a * second time to make sure that historical information is kept...) */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "older value", 2, false); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate an add at a later date that the previous delete. * conflict resolution should keep it */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "new value", 4, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000003000000000000:attrDel"); builder.add(DISPLAYNAME + ":0000000000000004000000000000:add:new value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that conflict between a modify-delete-attribute and modify-add * for multi-valued attributes are handled correctly. */ @Test() public void deleteAndReplace() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000004000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * simulate a delete of the whole description attribute done at time * t4 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, null, 4, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate a replace at an earlier date that the previous delete. The * conflict resolution should detect that this replace must be ignored. */ testModify(entry, hist, DESCRIPTION, ModificationType.REPLACE, "new value", 3, false); assertEquals(hist.encodeAndPurge(), attrDel); } /** * Test that conflict between a modify-replace and a a modify-delete * with some attribute values is resolved correctly. * This test has been created to reproduce Issue 3397. */ @Test() public void replaceAndDelete() throws Exception { // create an entry to use with conflicts tests. Entry entry = initializeEntry(); // // Create description with values value1 and value2 and add // this attribute to the entry. // AttributeBuilder builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); builder.add("value3"); builder.add("value4"); List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); entry.addAttribute(builder.toAttribute(), duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); // simulate a REPLACE of the attribute with values : value1, value2, value3 // at time t1. builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); builder.add("value3"); Modification mod = new Modification(ModificationType.REPLACE, builder.toAttribute()); testModify(entry, hist, DESCRIPTION, mod, 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:repl:value1"); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value2"); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value3"); assertEquals(hist.encodeAndPurge(),builder.toAttribute()); // simulate a DELETE of the attribute values : value3 and value4 // at time t2. builder = new AttributeBuilder(DESCRIPTION); builder.add("value3"); builder.add("value4"); mod = new Modification(ModificationType.DELETE, builder.toAttribute()); List<Modification> mods = replayModify(entry, hist, mod, 2); mod = mods.get(0); builder = new AttributeBuilder(DESCRIPTION); builder.add("value3"); assertEquals(mod.getAttribute(), builder.toAttribute()); assertEquals(mod.getModificationType(), ModificationType.DELETE); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:repl:value1"); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value2"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value3"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value4"); assertEquals(hist.encodeAndPurge(),builder.toAttribute()); } /** * Test that conflict between a modify-replace and a a modify-delete * with some attribute values is resolved correctly when they are replayed * disorderly. * This test has been created to reproduce Issue 3397. */ @Test() public void replaceAndDeleteDisorder() throws Exception { AttributeType descriptionAttrType = DirectoryServer.getSchema().getAttributeType(DESCRIPTION); // create an entry to use with conflicts tests. Entry entry = initializeEntry(); // We will reuse these attributes a couple of times. AttributeBuilder builder = new AttributeBuilder(DESCRIPTION); builder.add("value3"); builder.add("value4"); Attribute values3and4 = builder.toAttribute(); builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); Attribute values1and2 = builder.toAttribute(); // // Create description with values value1 and value2 and add // this attribute to the entry. // builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); builder.add("value3"); builder.add("value4"); List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); entry.addAttribute(builder.toAttribute(), duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); // simulate a DELETE of the attribute values : value3 and value4 // at time t2. Modification mod = new Modification(ModificationType.DELETE, values3and4); List<Modification> mods = replayModify(entry, hist, mod, 2); entry.applyModifications(mods); // check that the MOD is not altered by the replay mechanism. mod = mods.get(0); assertEquals(mod.getModificationType(), ModificationType.DELETE); assertEquals(mod.getAttribute(), values3and4); // check that the entry now contains value1 and value2 and no other values. Attribute resultEntryAttr = entry.getAttribute(descriptionAttrType).get(0); assertEquals(resultEntryAttr, values1and2); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value3"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value4"); assertEquals(hist.encodeAndPurge(),builder.toAttribute()); // simulate a REPLACE of the attribute with values : value1, value2, value3 // at time t1. builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); builder.add("value3"); mod = new Modification(ModificationType.REPLACE, builder.toAttribute()); mods = replayModify(entry, hist, mod, 1); entry.applyModifications(mods); mod = mods.get(0); // check that value3 has been removed from the MOD-REPLACE because // a later operation contains a MOD-DELETE of this value. assertEquals(mod.getModificationType(), ModificationType.REPLACE); assertEquals(mod.getAttribute(), values1and2); // check that the entry now contains value1 and value2 and no other values. assertEquals(resultEntryAttr, values1and2); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:repl:value1"); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value2"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value3"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value4"); assertEquals(hist.encodeAndPurge(),builder.toAttribute()); } /** * Test that conflict between a modify-delete-attribute and modify-add * for multi-valued attributes are handled correctly. */ @Test() public void deleteAndReplaceSingle() throws Exception { Entry entry = initializeEntry(); // Create a single valued attribute with value : "value1" // add this attribute to the entry. List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); Attribute attribute = Attributes.create(DISPLAYNAME, "value1"); entry.addAttribute(attribute, duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000004000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * simulate a delete of the whole description attribute done at time * t4 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 4, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate a replace at an earlier date that the previous delete. The * conflict resolution should detect that this replace must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "new value", 3, false); assertEquals(hist.encodeAndPurge(), attrDel); } /** * Test that conflict between a modify-add and modify-add for * multi-valued attributes are handled correctly. */ @Test() public void addAndAdd() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a add of the description attribute done at time t10 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "init value", 10, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add at an earlier date that the previous add. The * conflict resolution should detect that this add must be kept. */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "older value", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:older value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add with a value already existing. * The conflict resolution should remove this add. */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "init value", 13, false); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:older value"); builder.add(DESCRIPTION + ":000000000000000d000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add at a later date that the previous add. conflict * resolution should keep it */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "new value", 14, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:older value"); builder.add(DESCRIPTION + ":000000000000000d000000000000:add:init value"); builder.add(DESCRIPTION + ":000000000000000e000000000000:add:new value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that a DEL and ADD of the same attribute same value done * in a single operation correctly results in the value being kept. * * This is not a conflict in the strict definition but does exert * the conflict resolution code. */ @Test() public void delAndAddSameOp() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a add of the description attribute done at time t10 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "init value", 10, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a del and a add in the same operation */ Attribute attr = Attributes.create(DESCRIPTION, "init value"); Modification mod1 = new Modification(ModificationType.DELETE, attr); attr = Attributes.create(DESCRIPTION, "Init Value"); Modification mod2 = new Modification(ModificationType.ADD, attr); List<Modification> mods = new LinkedList<Modification>(); mods.add(mod1); mods.add(mod2); replayModifies(entry, hist, mods, 11); assertEquals(mods.size(), 2, "DEL and ADD of the same attribute same value was not correct"); assertEquals(mods.get(0), mod1, "DEL and ADD of the same attribute same value was not correct"); assertEquals(mods.get(1), mod2, "DEL and ADD of the same attribute same value was not correct"); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000b000000000000:add:Init Value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); } /** * Test that a DEL of one value and REPLACE with no value, of the same * attribute done in a single operation correctly results in the value * being kept. * * This is not a conflict in the strict definition but does exert * the conflict resolution code. */ @Test() public void delAndReplaceSameOp() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000c000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * simulate a add of the description attribute done at time t10 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "init value", 10, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a add of the description attribute done at time t10 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "second value", 11, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000a000000000000:add:init value"); builder.add(DESCRIPTION + ":000000000000000b000000000000:add:second value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate a delete of one value and a replace with no value * in the same operation */ Attribute attr = Attributes.create(DESCRIPTION, "init value"); Modification mod1 = new Modification(ModificationType.DELETE, attr); Attribute attr2 = Attributes.empty(DESCRIPTION); Modification mod2 = new Modification(ModificationType.REPLACE, attr2); List<Modification> mods = new LinkedList<Modification>(); mods.add(mod1); mods.add(mod2); List<Modification> mods2 = new LinkedList<Modification>(mods); replayModifies(entry, hist, mods, 12); assertEquals(hist.encodeAndPurge(), attrDel); assertEquals(mods.size(), 2, "DEL one value, del by Replace of the same attribute was not correct"); assertEquals(mods.get(0), mod1, "DEL one value, del by Replace of the same attribute was not correct"); assertEquals(mods.get(1), mod2, "DEL one value, del by Replace of the same attribute was not correct"); // Replay the same modifs again replayModifies(entry, hist, mods2, 12); assertEquals(hist.encodeAndPurge(), attrDel); assertEquals(mods2.size(), 2, "DEL one value, del by Replace of the same attribute was not correct"); } /** * Test that a ADD and DEL of the same attribute same value done * in a single operation correctly results in the value being kept. * * This is not a conflict in the strict definition but does exert * the conflict resolution code. */ @Test() public void addAndDelSameOp() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * Now simulate a del and a add in the same operation */ Attribute attr = Attributes.create(DESCRIPTION, "init value"); Modification mod1 = new Modification(ModificationType.ADD, attr); attr = Attributes.create(DESCRIPTION, "Init Value"); Modification mod2 = new Modification(ModificationType.DELETE, attr); List<Modification> mods = new LinkedList<Modification>(); mods.add(mod1); mods.add(mod2); replayModifies(entry, hist, mods, 11); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":000000000000000b000000000000:del:Init Value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); assertEquals(mods.size(), 2, "DEL and ADD of the same attribute same value was not correct"); assertEquals(mods.get(0), mod1, "DEL and ADD of the same attribute same value was not correct"); assertEquals(mods.get(1), mod2, "DEL and ADD of the same attribute same value was not correct"); } /** * Test that conflict between two modify-add with for * multi-valued attributes are handled correctly when some of the values * are the same : * - first ADD done with value1 * - second ADD done with value1 and value2 * This test has been created to make sure that issue 3394 is fixed. */ @Test() public void addAndAddSameValues() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a add of the description attribute done at time 1 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value1", 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate an add of the description attribute values * "value1" and "value2" done at time 2 */ testModify(entry, hist, DESCRIPTION, buildModWith2Vals(DESCRIPTION, ModificationType.ADD, "value1", "value2"), 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value1"); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value2"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); // Check that entry now only contains the 2 attribute values List<Attribute> attrs = entry.getAttribute(DESCRIPTION); Attribute attr = attrs.get(0); assertEquals(2, attr.size()); attr.contains(AttributeValues.create(attr.getAttributeType(), "value1")); attr.contains(AttributeValues.create(attr.getAttributeType(), "value2")); // do the same as before but in reverse order entry = initializeEntry(); // load historical from the entry hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate an add of the description attribute values * "value1" and "value2" done at time 1 */ testModify(entry, hist, DESCRIPTION, buildModWith2Vals(DESCRIPTION, ModificationType.ADD, "value1", "value2"), 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value1"); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value2"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a add of the description attribute done at time 1 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value1", 2, false); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value2"); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); // Check that entry now only contains the 2 attribute values attrs = entry.getAttribute(DESCRIPTION); attr = attrs.get(0); assertEquals(2, attr.size()); attr.contains(AttributeValues.create(attr.getAttributeType(), "value1")); attr.contains(AttributeValues.create(attr.getAttributeType(), "value2")); } /** * Test that conflict between a modify-add and modify-add for * single-valued attributes are handled correctly. */ @Test() public void addAndAddSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:older value"); Attribute olderValue = builder.toAttribute(); /* * simulate a add of the displayName attribute done at time t10 */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "init value", 10, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":000000000000000a000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * Now simulate an add at an earlier date that the previous add. The * conflict resolution should detect that this add must be kept. * and that the previous value must be discarded, and therefore * turn the add into a replace. */ Modification mod = buildMod(DISPLAYNAME, ModificationType.ADD, "older value"); List<Modification> mods = replayModify(entry, hist, mod, 1); assertEquals(hist.encodeAndPurge(), olderValue); /* * After replay the mods should contain only one mod, * the mod should now be a replace with the older value. */ testMods(mods, 1, ModificationType.REPLACE, "older value"); /* * Now simulate a new value at a later date. * The conflict modify code should detect that there is already a value * and skip this change. */ mod = buildMod(DISPLAYNAME, ModificationType.ADD, "new value"); mods = replayModify(entry, hist, mod, 2); assertEquals(hist.encodeAndPurge(), olderValue); assertTrue(mods.isEmpty()); } /** * Test that conflict between add, delete and add on aingle valued attribute * are handled correctly. */ @Test() public void addDelAddSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000003000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * simulate a add of the displayName attribute done at time t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "init value", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:init value"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a del of the displayName attribute done at time t3 * this should be processed normally */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "init value", 3, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Now simulate another add, that would come from another master * and done at time t2 (between t1 and t2) * This add should not be processed. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "second value", 2, false); assertEquals(hist.encodeAndPurge(), attrDel); } /** * Test that conflict between add, add and delete on single valued attributes * are handled correctly. * * This test simulate the case where a simple add is done on a first server * and at a later date but before replication happens, a add followed by * a delete of the second value is done on another server. * The test checks that the first value wins and stays in the entry. */ @Test() public void addAddDelSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:first value"); Attribute firstValue = builder.toAttribute(); /* * simulate a add of the displayName attribute done at time t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "first value", 1, true); assertEquals(hist.encodeAndPurge(), firstValue); /* * simulate a add of the displayName attribute done at time t2 * with a second value. This should not work because there is already * a value */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "second value", 2, false); assertEquals(hist.encodeAndPurge(), firstValue); /* * Now simulate a delete of the second value. * The delete should not be accepted because it is done on a value * that did not get into the entry. */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "second value", 2, false); assertEquals(hist.encodeAndPurge(), firstValue); } /** * Check that the mods given as first parameter match the next parameters. * * @param mods The mods that must be tested. * @param size the size that the mods must have. * @param modType the type of Modification that the first mod of the * mods should have. * @param value the value that the first mod of the mods should have. */ private void testMods( List<Modification> mods, int size, ModificationType modType, String value) { assertEquals(size, mods.size()); Modification newMod = mods.get(0); assertTrue(newMod.getModificationType().equals(modType)); AttributeValue val = newMod.getAttribute().iterator().next(); assertEquals(val.getValue().toString(), value); } /** * Create an initialize an entry that can be used for modify conflict * resolution tests. */ private Entry initializeEntry() throws DirectoryException { AttributeType entryuuidAttrType = DirectoryServer.getSchema().getAttributeType( EntryHistorical.ENTRYUUID_ATTRIBUTE_NAME); /* * Objectclass and DN do not have any impact on the modify conflict * resolution for the description attribute. Always use the same values * for all these tests. */ DN dn = DN.decode(TEST_ROOT_DN_STRING); Map<ObjectClass, String> objectClasses = new HashMap<ObjectClass, String>(); ObjectClass org = DirectoryServer.getObjectClass(ORGANIZATION); objectClasses.put(org, ORGANIZATION); /* * start with a new entry with an empty attribute */ Entry entry = new Entry(dn, objectClasses, null, null); // Construct a new random UUID. and add it into the entry UUID uuid = UUID.randomUUID(); // Create the att values list ArrayList<Attribute> uuidList = new ArrayList<Attribute>(1); Attribute uuidAttr = Attributes.create(entryuuidAttrType, uuid .toString()); uuidList.add(uuidAttr); /* * Add the uuid in the entry */ Map<AttributeType, List<Attribute>> operationalAttributes = entry .getOperationalAttributes(); operationalAttributes.put(entryuuidAttrType, uuidList); return entry; } /* * helper function. */ private void testHistoricalAndFake( EntryHistorical hist, Entry entry) { AttributeType entryuuidAttrType = DirectoryServer.getSchema().getAttributeType(EntryHistorical.ENTRYUUID_ATTRIBUTE_NAME); // Get the historical uuid associated to the entry // (the one that needs to be tested) String uuid = EntryHistorical.getEntryUUID(entry); // Get the Entry uuid in String format List<Attribute> uuidAttrs = entry .getOperationalAttribute(entryuuidAttrType); uuidAttrs.get(0).iterator().next().toString(); if (uuidAttrs != null) { if (uuidAttrs.size() > 0) { Attribute att = uuidAttrs.get(0); String retrievedUuid = (att.iterator().next()).toString(); assertTrue(retrievedUuid.equals(uuid)); } } // Test FakeOperation try { Iterable<FakeOperation> fks = EntryHistorical.generateFakeOperations(entry); if (fks.iterator().hasNext()) { FakeOperation fk = fks.iterator().next(); assertTrue(new FakeOperationComparator().compare(fk, fk) == 0); assertTrue(new FakeOperationComparator().compare(null , fk) < 0); ReplicationMsg generatedMsg = fk.generateMessage() ; if (generatedMsg instanceof LDAPUpdateMsg) { LDAPUpdateMsg new_name = (LDAPUpdateMsg) generatedMsg; assertEquals(new_name.getEntryUUID(),uuid); } } } catch (RuntimeException e) { assertTrue(false) ; } } /** * * */ private void testModify(Entry entry, EntryHistorical hist, String attrName, ModificationType modType, String value, int date, boolean keepChangeResult) throws DirectoryException { Modification mod = buildMod(attrName, modType, value); testModify(entry, hist, attrName, mod, date, keepChangeResult); } /** * */ private void testModify(Entry entry, EntryHistorical hist, String attrName, Modification mod, int date, boolean keepChangeResult) throws DirectoryException { List<Modification> mods = replayModify(entry, hist, mod, date); if (keepChangeResult) { /* * The last change should have been detected as newer and * should be kept by the conflict resolution code. */ assertTrue(mods.contains(mod)); assertEquals(1, mods.size()); } else { /* * The last older change should have been detected as conflicting and * should be removed by the conflict resolution code. */ assertFalse(mods.contains(mod)); assertEquals(0, mods.size()); } entry.applyModifications(mods); } /** * */ private void replayModifies( Entry entry, EntryHistorical hist, List<Modification> mods, int date) { InternalClientConnection aConnection = InternalClientConnection.getRootConnection(); ChangeNumber t = new ChangeNumber(date, 0, 0); ModifyOperationBasis modOpBasis = new ModifyOperationBasis(aConnection, 1, 1, null, entry.getDN(), mods); LocalBackendModifyOperation modOp = new LocalBackendModifyOperation(modOpBasis); ModifyContext ctx = new ModifyContext(t, "uniqueId"); modOp.setAttachment(SYNCHROCONTEXT, ctx); hist.replayOperation(modOp, entry); } private List<Modification> replayModify( Entry entry, EntryHistorical hist, Modification mod, int date) { AttributeType historicalAttrType = DirectoryServer.getSchema().getAttributeType( EntryHistorical.HISTORICAL_ATTRIBUTE_NAME); InternalClientConnection aConnection = InternalClientConnection.getRootConnection(); ChangeNumber t = new ChangeNumber(date, 0, 0); List<Modification> mods = new ArrayList<Modification>(); mods.add(mod); ModifyOperationBasis modOpBasis = new ModifyOperationBasis(aConnection, 1, 1, null, entry.getDN(), mods); LocalBackendModifyOperation modOp = new LocalBackendModifyOperation(modOpBasis); ModifyContext ctx = new ModifyContext(t, "uniqueId"); modOp.setAttachment(SYNCHROCONTEXT, ctx); hist.replayOperation(modOp, entry); if (mod.getModificationType() == ModificationType.ADD) { AddOperationBasis addOpBasis = new AddOperationBasis(aConnection, 1, 1, null, entry .getDN(), entry.getObjectClasses(), entry.getUserAttributes(), entry.getOperationalAttributes()); LocalBackendAddOperation addOp = new LocalBackendAddOperation(addOpBasis); testHistorical(hist, addOp); } else { testHistoricalAndFake(hist, entry); } /* * Check that the encoding decoding of historical information * works by encoding decoding and checking that the result is the same * as the initial value. */ entry.removeAttribute(historicalAttrType); entry.addAttribute(hist.encodeAndPurge(), null); EntryHistorical hist2 = EntryHistorical.newInstanceFromEntry(entry); assertEquals(hist2.encodeAndPurge(), hist.encodeAndPurge()); return mods; } private Modification buildMod( String attrName, ModificationType modType, String value) { /* create AttributeType that will be used for this test */ Attribute attr; if (value != null) { attr = Attributes.create(attrName, value); } else { attr = Attributes.empty(attrName); } Modification mod = new Modification(modType, attr); return mod; } private Modification buildModWith2Vals( String attrName, ModificationType modType, String value1, String value2) { AttributeBuilder builder = new AttributeBuilder(attrName); builder.add(value1); builder.add(value2); Modification mod = new Modification(modType, builder.toAttribute()); return mod; } /** * */ private void testHistorical( EntryHistorical hist, LocalBackendAddOperation addOp) { AttributeType entryuuidAttrType = DirectoryServer.getSchema().getAttributeType( EntryHistorical.ENTRYUUID_ATTRIBUTE_NAME); // Get the historical uuid associated to the entry // (the one that needs to be tested) String uuid = EntryHistorical.getEntryUUID(addOp); // Get the op uuid in String format List<Attribute> uuidAttrs = addOp.getOperationalAttributes().get( entryuuidAttrType); uuidAttrs.get(0).iterator().next().toString(); if (uuidAttrs != null) { if (uuidAttrs.size() > 0) { Attribute att = uuidAttrs.get(0); String retrievedUuid = (att.iterator().next()).toString(); assertTrue(retrievedUuid.equals(uuid)); } } } /** * Test that a single replicated modify operation, that contains a * modify-add of a value followed by modify-delete of that value * is handled properly. */ @Test() public void addDeleteSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "aValue", 1, true); /* * simulate a delete of same value in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "aValue", 1, true); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test that a single replicated modify operation, that contains a * modify-add of a value followed by modify-delete of the attribute * is handled properly. */ @Test() public void addDeleteAttrSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "aValue", 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:aValue"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test that the replay of a single replicated modify operation, * that contains a modify-add of a value followed by modify-delete * of the same value is handled properly. */ @Test() public void replayAddDeleteSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000002000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "aValue", 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000002000000000000:add:aValue"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "aValue", 2, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Redo the same operations. This time, we expect them not to be applied. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "aValue", 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000002000000000000:add:aValue"); builder.add(DISPLAYNAME + ":0000000000000002000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "aValue", 2, true); assertEquals(hist.encodeAndPurge(), attrDel); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test we can del an existing value and add a new one, and then replay * a del of the same existing value and add of a different new one. */ @Test() public void replayDelAddDifferent() throws Exception { Entry entry = initializeEntry(); // // Create description with values value1 and value2 and add // this attribute to the entry. // AttributeBuilder builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); entry.addAttribute(builder.toAttribute(), duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a delete of same value in the same operation done at time * t1 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value1", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate an add of new value in the same operation done at time * t1 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value3", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value3"); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of same value in the same operation done at time * t2 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value1", 2, false); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value3"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate an add of new value in the same operation done at time * t2 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value4", 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value3"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value4"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DESCRIPTION); builder = new AttributeBuilder(DESCRIPTION); builder.add("value2"); builder.add("value3"); builder.add("value4"); assertEquals(attrs.get(0), builder.toAttribute()); } /** * Test we can del an existing value and add a new one, and then replay * a del of another existing value and add of the same one. */ @Test() public void replayDelAddSame() throws Exception { Entry entry = initializeEntry(); // // Create description with values value1 and value2 and add // this attribute to the entry. // AttributeBuilder builder = new AttributeBuilder(DESCRIPTION); builder.add("value1"); builder.add("value2"); builder.add("value3"); List<AttributeValue> duplicateValues = new LinkedList<AttributeValue>(); entry.addAttribute(builder.toAttribute(), duplicateValues); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * simulate a delete of a value in the same operation done at time * t1 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value1", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate an add of new value in the same operation done at time * t1 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value4", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value4"); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of another value in the same operation done at time * t2 */ testModify(entry, hist, DESCRIPTION, ModificationType.DELETE, "value2", 2, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000001000000000000:add:value4"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value2"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate an add of already added value in the same operation done at time * t2 */ testModify(entry, hist, DESCRIPTION, ModificationType.ADD, "value4", 2, false); builder = new AttributeBuilder(SYNCHIST); builder.add(DESCRIPTION + ":0000000000000001000000000000:del:value1"); builder.add(DESCRIPTION + ":0000000000000002000000000000:del:value2"); builder.add(DESCRIPTION + ":0000000000000002000000000000:add:value4"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DESCRIPTION); builder = new AttributeBuilder(DESCRIPTION); builder.add("value3"); builder.add("value4"); assertEquals(attrs.get(0), builder.toAttribute()); } /** * Test that the replay of a single replicated modify operation, * that contains a modify-add of a value followed by modify-delete * of the attribute is handled properly. */ @Test() public void replayAddDeleteAttrSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "aValue", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:aValue"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 1, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Redo the same operations. This time, we expect them not to be applied. */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "aValue", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:aValue"); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 1, true); assertEquals(hist.encodeAndPurge(), attrDel); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test that a single replicated modify operation, that contains a * modify-replace of a value followed by modify-delete of that value * is handled properly. */ @Test() public void replaceDeleteSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:repl:aValue"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of same value in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "aValue", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test that a single replicated modify operation, that contains a * modify-replace of a value followed by modify-delete of the attribute * is handled properly. */ @Test() public void replaceDeleteAttrSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:repl:aValue"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test that the replay of a single replicated modify operation, * that contains a modify-replace of a value followed by modify-delete * of the same value is handled properly. */ @Test() public void replayReplaceDeleteSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000002000000000000:repl:aValue"); Attribute repl = builder.toAttribute(); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000002000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 2, true); assertEquals(hist.encodeAndPurge(), repl); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "aValue", 2, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Redo the same operations. This time, we expect them not to be applied. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 2, true); assertEquals(hist.encodeAndPurge(), repl); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "aValue", 2, true); assertEquals(hist.encodeAndPurge(), attrDel); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test that the replay of a single replicated modify operation, * that contains a modify-replace of a value followed by modify-delete * of the attribute is handled properly. */ @Test() public void replayReplaceDeleteAttrSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:repl:aValue"); Attribute repl = builder.toAttribute(); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); Attribute attrDel = builder.toAttribute(); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 1, true); assertEquals(hist.encodeAndPurge(), repl); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 1, true); assertEquals(hist.encodeAndPurge(), attrDel); /* * Redo the same operations. This time, we expect them not to be applied. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 1, true); assertEquals(hist.encodeAndPurge(), repl); /* * simulate a delete of the attribute in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 1, true); assertEquals(hist.encodeAndPurge(), attrDel); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); assertNull(attrs); } /** * Test that a single replicated modify operation, that contains a * modify-replace of a value followed by modify-delete of that value, * followed by a modify-add of a new value is handled properly. */ @Test() public void replaceDeleteAddSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:repl:aValue"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of same value in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, "aValue", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate an add of new value in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "NewValue", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:NewValue"); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* The entry should have one value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); Attribute attr = attrs.get(0); assertEquals(1, attr.size()); attr.contains(AttributeValues.create(attr.getAttributeType(), "NewValue")); } /** * Test that a single replicated modify operation, that contains a * modify-replace of a value followed by modify-delete of the attribute, * followed by a modify-add of a new value is handled properly. */ @Test() public void replaceDeleteAttrAddSameOpSingle() throws Exception { Entry entry = initializeEntry(); // load historical from the entry EntryHistorical hist = EntryHistorical.newInstanceFromEntry(entry); /* * Add at time t1 that the previous delete. The * conflict resolution should detect that this add must be ignored. */ testModify(entry, hist, DISPLAYNAME, ModificationType.REPLACE, "aValue", 1, true); AttributeBuilder builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:repl:aValue"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate a delete of same value in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.DELETE, null, 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* * simulate an add of new value in the same operation done at time * t1 */ testModify(entry, hist, DISPLAYNAME, ModificationType.ADD, "NewValue", 1, true); builder = new AttributeBuilder(SYNCHIST); builder.add(DISPLAYNAME + ":0000000000000001000000000000:add:NewValue"); builder.add(DISPLAYNAME + ":0000000000000001000000000000:attrDel"); assertEquals(hist.encodeAndPurge(), builder.toAttribute()); /* The entry should have no value */ List<Attribute> attrs = entry.getAttribute(DISPLAYNAME); Attribute attr = attrs.get(0); assertEquals(1, attr.size()); attr.contains(AttributeValues.create(attr.getAttributeType(), "NewValue")); } }