/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006 Sun Microsystems, Inc. */ package org.opends.server.synchronization; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.testng.Assert.*; import static org.opends.server.synchronization.OperationContext.*; import org.opends.server.SchemaFixture; import org.opends.server.core.DirectoryServer; import org.opends.server.core.ModifyOperation; import org.opends.server.protocols.internal.InternalClientConnection; import org.opends.server.types.Attribute; import org.opends.server.types.AttributeType; import org.opends.server.types.AttributeValue; import org.opends.server.types.DN; import org.opends.server.types.Entry; import org.opends.server.types.Modification; import org.opends.server.types.ModificationType; import org.opends.server.types.ObjectClass; /* * Test the conflict resolution for modify operations * 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 */ public class ModifyConflictTest extends SynchronizationTestCase { /** * Test that conflict between a modify-replace and modify-add * for multi-valued attributes are handled correctly. */ @Test() public void replaceAndAdd() throws Exception { /* * Objectclass and DN do not have any impact on the modifty conflict * resolution for the description attribute. * Always use the same values for all these tests. */ DN dn = DN.decode("dc=com"); Map objectClasses = new HashMap(); ObjectClass org = DirectoryServer.getObjectClass("organization"); objectClasses.put(org, "organization"); /* * start with a new entry with an empty description */ Entry entry = new Entry(dn, objectClasses, null, null); Historical hist = Historical.load(entry); /* * simulate a modify-replace done at time t10 */ testModify(entry, hist, "description", ModificationType.REPLACE, "init value", 10, true); /* * 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); /* * 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); /* * 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); } /** * Test that conflict between a modify-delete-attribute and modify-add * for multi-valued attributes are handled correctly. */ @Test() public void deleteAndAdd() throws Exception { /* * Objectclass and DN do not have any impact on the modifty conflict * resolution for the description attribute. * Always use the same values for all these tests. */ DN dn = DN.decode("dc=com"); Map objectClasses = new HashMap(); ObjectClass org = DirectoryServer.getObjectClass("organization"); objectClasses.put(org, "organization"); /* * start with a new entry with an empty description */ Entry entry = new Entry(dn, objectClasses, null, null); Historical hist = Historical.load(entry); /* * simulate a delete of the whole description attribute done at time t10 */ testModify(entry, hist, "description", ModificationType.DELETE, null, 10, true); /* * 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); /* * 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); /* * 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); } /** * Test that conflict between a modify-add and modify-add * for multi-valued attributes are handled correctly. */ @Test() public void addAndAdd() throws Exception { /* * Objectclass and DN do not have any impact on the modifty conflict * resolution for the description attribute. * Always use the same values for all these tests. */ DN dn = DN.decode("dc=com"); Map objectClasses = new HashMap(); ObjectClass org = DirectoryServer.getObjectClass("organization"); objectClasses.put(org, "organization"); /* * start with a new entry with an empty description */ Entry entry = new Entry(dn, objectClasses, null, null); Historical hist = Historical.load(entry); /* * simulate a add of the description attribute done at time t10 */ testModify(entry, hist, "description", ModificationType.ADD, "init value", 10, true); /* * 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); /* * Now simulate an add at an earlier date that the previous add. * The conflict resolution should detect that this add must be kept. * (a second time to make sure that historical information is kept...) */ testModify(entry, hist, "description", ModificationType.ADD, "older value", 2, false); /* * 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", 11, true); } /* * helper function. */ private static void testModify(Entry entry, Historical hist, String attrName, ModificationType modType, String value, int date, boolean keepChangeResult) { InternalClientConnection connection = new InternalClientConnection(); ChangeNumber t = new ChangeNumber(date, (short) 0, (short) 0); /* create AttributeType description that will be usedfor this test */ AttributeType attrType = DirectoryServer.getAttributeType(attrName, true); LinkedHashSet values = new LinkedHashSet(); if (value != null) values.add(new AttributeValue(attrType, value)); Attribute attr = new Attribute(attrType, attrName, values); List mods = new ArrayList(); Modification mod = new Modification(modType, attr); mods.add(mod); ModifyOperation modOp = new ModifyOperation(connection, 1, 1, null, entry.getDN(), mods); ModifyContext ctx = new ModifyContext(t, "uniqueId"); modOp.setAttachment(SYNCHROCONTEXT, ctx); hist.replayOperation(modOp, entry); /* * The last older change should have been detected as conflicting * and should be removed by the conflict resolution code. */ if (keepChangeResult) { assertTrue(mods.contains(mod)); assertEquals(1, mods.size()); } else { assertFalse(mods.contains(mod)); assertEquals(0, mods.size()); } } /** * Set up the environment for performing the tests in this suite. * * @throws Exception * If the environment could not be set up. */ @BeforeClass public void setUp() throws Exception { // This test suite depends on having the schema available. SchemaFixture.FACTORY.setUp(); } /** * Tears down the environment for performing the tests in this suite. * * @throws Exception * If the environment could not be finalized. */ @AfterClass public void tearDown() throws Exception { SchemaFixture.FACTORY.tearDown(); } }