You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by sa...@apache.org on 2011/11/01 20:29:20 UTC

svn commit: r1196230 - in /directory/apacheds/branches/apacheds-txns/core/src: main/java/org/apache/directory/server/core/txn/ main/java/org/apache/directory/server/core/txn/logedit/ test/java/org/apache/directory/server/core/txn/

Author: saya
Date: Tue Nov  1 19:29:19 2011
New Revision: 1196230

URL: http://svn.apache.org/viewvc?rev=1196230&view=rev
Log:
Test for merging updates to an entry from the txn log. This kind of merge is used to get a transactionally consistent view of an entry.

Also added txnconflict test to check if conflicts on Dn space can be detected. This kind of check is done during commit time.

Fixes and documentation based on the above two tests.

Added:
    directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/EntryUpdateMergeTest.java
    directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/TxnConflicTest.java
Modified:
    directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/DefaultTxnLogManager.java
    directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/ReadWriteTxn.java
    directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/logedit/DataChangeContainer.java

Modified: directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/DefaultTxnLogManager.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/DefaultTxnLogManager.java?rev=1196230&r1=1196229&r2=1196230&view=diff
==============================================================================
--- directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/DefaultTxnLogManager.java (original)
+++ directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/DefaultTxnLogManager.java Tue Nov  1 19:29:19 2011
@@ -166,7 +166,7 @@ public class DefaultTxnLogManager<ID> im
      */
     public void addWrite( Dn baseDn, SearchScope scope )
     {
-        addDnSet( baseDn, scope, true );
+        addDnSet( baseDn, scope, false );
     }
 
 
@@ -197,6 +197,9 @@ public class DefaultTxnLogManager<ID> im
         else
         {
             txn.addWrite( dnSet );
+            
+            // Every written dn set is also read
+            txn.addRead( dnSet );
         }
     }
 }
\ No newline at end of file

Modified: directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/ReadWriteTxn.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/ReadWriteTxn.java?rev=1196230&r1=1196229&r2=1196230&view=diff
==============================================================================
--- directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/ReadWriteTxn.java (original)
+++ directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/ReadWriteTxn.java Tue Nov  1 19:29:19 2011
@@ -245,102 +245,90 @@ import org.apache.directory.shared.ldap.
         }
     }
 
-
+    /**
+     * Applies the updates made by this txn to the entry identified by the entryID and partition dn. 
+     *
+     * @param partitionDn dn of the partition of the entry
+     * @param entryID id of the entry
+     * @param curEntry entry to be merged
+     * @param cloneOnChange true if entry should be cloned while applying a change.
+     * @return entry after it is merged with the updates in the txn.
+     */
     public Entry applyUpdatesToEntry( Dn partitionDn, ID entryID, Entry curEntry, boolean cloneOnChange )
     {
         boolean needToCloneOnChange = cloneOnChange;
         LogEdit<ID> edit;
         DataChangeContainer<ID> container;
-        
+
         Iterator<LogEdit<ID>> it = logEdits.iterator();
-        
+
         while ( it.hasNext() )
         {
             edit = it.next();
-            
+
             if ( edit instanceof DataChangeContainer )
             {
-                container = (DataChangeContainer<ID>)edit;
-                
+                container = ( DataChangeContainer<ID> ) edit;
+
                 /**
                  * Check if the container has changes for the entry
                  * and the version says we need to apply this change
                  */
-                //TODO check version and id here. If uuid is not available,
-                // then match partitionDn as well.
-                String uuid = container.getUUID();
-                boolean applyChanges = false; 
-                
-                if ( uuid != null )
+                //TODO check version and id here. 
+                ID entryId = container.getEntryID();
+                boolean applyChanges = false;
+
+                if ( entryId != null )
                 {
                     /*
                      * Container has changes for entry. Check if the entry change
-                     * affects out entry by comparing uuid if entry is available.
-                     * Otherwise compare partition dn and Id.
+                     * affects out entry by comparing id and partitionDn.
                      */
-                    
-                    if ( curEntry!= null )
-                    {
-                        String curUuid = null;  
-                        
-                        try
-                        {
-                            curUuid = curEntry.get( SchemaConstants.ENTRY_UUID_AT ).getString();
-                            if ( curUuid.equals( uuid ) )
-                            {
-                                //TODO check the version here to see if the change should be applied
-                            }
-                        }
-                        catch( LdapException e )
-                        {
-                            //TODO decide whether to throw IOException or an internal exception here
-                        }
-                    }
-                    else
+
+                    Comparator<ID> idComp = TxnManagerFactory.<ID> txnManagerInstance().getIDComparator();
+
+                    if ( partitionDn.equals( container.getPartitionDn() )
+                        && ( idComp.compare( entryID, container.getEntryID() ) == 0 ) )
                     {
-                        Comparator<ID> idComp = TxnManagerFactory.<ID>txnManagerInstance().getIDComparator();
-                        
-                        if ( partitionDn.equals( container.getPartitionDn() ) &&  ( idComp.compare( entryID, container.getEntryID() ) == 0 ))
-                        {
-                            applyChanges = true;
-                        }
+                        applyChanges = true;
                     }
+
                 }
-                
+
                 if ( applyChanges )
                 {
-                    List<DataChange<ID>> dataChanges =  container.getChanges();
+                    List<DataChange<ID>> dataChanges = container.getChanges();
                     Iterator<DataChange<ID>> dit = dataChanges.iterator();
                     DataChange<ID> nextChange;
-                    
+
                     while ( dit.hasNext() )
                     {
                         nextChange = dit.next();
-                        
+
                         if ( ( nextChange instanceof EntryChange ) && ( curEntry != null ) )
                         {
-                            EntryChange<ID> entryChange = (EntryChange<ID>)nextChange;
-                           
+                            EntryChange<ID> entryChange = ( EntryChange<ID> ) nextChange;
+
                             if ( needToCloneOnChange )
                             {
                                 curEntry = curEntry.clone();
                                 needToCloneOnChange = false;
                             }
-                            
+
                             try
                             {
-                                AttributeUtils.applyModification(curEntry, entryChange.getRedoChange());
+                                AttributeUtils.applyModification( curEntry, entryChange.getRedoChange() );
                             }
-                            catch( LdapException e )
+                            catch ( LdapException e )
                             {
                                 //TODO decide whether to throw IOException or an internal exception here
                             }
                         }
                         else if ( nextChange instanceof EntryAddDelete )
                         {
-                            EntryAddDelete<ID> addDelete = (EntryAddDelete<ID>)nextChange;
+                            EntryAddDelete<ID> addDelete = ( EntryAddDelete<ID> ) nextChange;
                             needToCloneOnChange = false;
-                            
+
                             if ( addDelete.getType() == EntryAddDelete.Type.ADD )
                             {
                                 curEntry = addDelete.getChangedEntry();
@@ -351,11 +339,11 @@ import org.apache.directory.shared.ldap.
                             }
                         }
                     }
-                    
+
                 }
             }
         }
-        
+
         return curEntry;
     }
     
@@ -446,12 +434,22 @@ import org.apache.directory.shared.ldap.
     }
 
 
+    /**
+     * Adds the given Dn to the read set of the current txn
+     *
+     * @param readSet dn to add
+     */
     public void addRead( DnSet readSet )
     {
         readDns.add( readSet );
     }
 
 
+    /**
+     * Adds the given Dn to the write and read set of the current txn.
+     *
+     * @param writeSet dn to add
+     */
     public void addWrite( DnSet writeSet )
     {
         writeDns.add( writeSet );
@@ -467,6 +465,13 @@ import org.apache.directory.shared.ldap.
     }
 
 
+    /**
+     * Checks if this txn's read set conflicts with the write set
+     * of the given txn.
+     *
+     * @param txn txn to verify this txn against
+     * @return true if a conflict is detected.
+     */
     public boolean hasConflict( ReadWriteTxn txn )
     {
         boolean result = false;

Modified: directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/logedit/DataChangeContainer.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/logedit/DataChangeContainer.java?rev=1196230&r1=1196229&r2=1196230&view=diff
==============================================================================
--- directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/logedit/DataChangeContainer.java (original)
+++ directory/apacheds/branches/apacheds-txns/core/src/main/java/org/apache/directory/server/core/txn/logedit/DataChangeContainer.java Tue Nov  1 19:29:19 2011
@@ -43,9 +43,6 @@ import org.apache.directory.server.core.
  */
 public class DataChangeContainer<ID> extends AbstractLogEdit<ID>
 {
-    /** Set to the uuid of the entry if the container contains a change for the entry, null otherwise */
-    private String uuid;
-
     /** id of the entry if the container contains a change for an entry */
     private ID entryID;
 
@@ -72,18 +69,6 @@ public class DataChangeContainer<ID> ext
     }
 
 
-    public String getUUID()
-    {
-        return uuid;
-    }
-
-
-    public void setUUID( String entryUUID )
-    {
-        this.uuid = entryUUID;
-    }
-
-
     public long getTxnID()
     {
         return txnID;
@@ -124,13 +109,6 @@ public class DataChangeContainer<ID> ext
     public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
     {
         Serializer idSerializer = TxnManagerFactory.txnManagerInstance().getIDSerializer();
-        boolean uuidNotNull = in.readBoolean();
-
-        if ( uuidNotNull )
-        {
-            uuid = in.readUTF();
-        }
-
         int len = in.readInt();
 
         if ( len < 0 )
@@ -165,17 +143,7 @@ public class DataChangeContainer<ID> ext
     {
         Serializer idSerializer = TxnManagerFactory.txnManagerInstance().getIDSerializer();
         DataChange<ID> change;
-
-        if ( uuid != null )
-        {
-            out.writeBoolean( true );
-            out.writeUTF( uuid );
-        }
-        else
-        {
-            out.writeBoolean( false );
-        }
-
+        
         if ( entryID == null )
         {
             out.writeInt( -1 );

Added: directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/EntryUpdateMergeTest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/EntryUpdateMergeTest.java?rev=1196230&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/EntryUpdateMergeTest.java (added)
+++ directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/EntryUpdateMergeTest.java Tue Nov  1 19:29:19 2011
@@ -0,0 +1,266 @@
+package org.apache.directory.server.core.txn;
+
+
+import java.io.IOException;
+import java.util.TreeSet;
+
+import org.apache.directory.shared.ldap.model.name.Dn;
+import org.apache.directory.shared.ldap.model.schema.AttributeType;
+import org.apache.directory.shared.ldap.model.schema.SchemaManager;
+import org.apache.directory.shared.ldap.model.entry.Attribute;
+import org.apache.directory.shared.ldap.model.entry.DefaultAttribute;
+import org.apache.directory.shared.ldap.model.entry.DefaultEntry;
+import org.apache.directory.shared.ldap.model.entry.DefaultModification;
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.Modification;
+import org.apache.directory.shared.ldap.model.entry.ModificationOperation;
+import org.apache.directory.shared.ldap.schemamanager.impl.DefaultSchemaManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.junit.Test;
+
+import org.apache.directory.server.core.log.InvalidLogException;
+import org.apache.directory.server.core.txn.IndexCursorWrapperTest.MockIndex;
+import org.apache.directory.server.core.txn.logedit.EntryChange;
+import org.apache.directory.server.core.txn.logedit.EntryAddDelete;
+import org.apache.directory.server.core.txn.logedit.DataChangeContainer;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.directory.server.core.api.partition.index.MasterTable;
+import org.apache.directory.server.core.api.partition.index.AbstractTable;
+
+
+public class EntryUpdateMergeTest
+{
+    /** Test partition Dn */
+    private Dn dn;
+
+    /** Log buffer size : 4096 bytes */
+    private int logBufferSize = 1 << 12;
+
+    /** Log File Size : 8192 bytes */
+    private long logFileSize = 1 << 13;
+
+    /** log suffix */
+    private static String LOG_SUFFIX = "log";
+
+    /** Txn manager */
+    private TxnManagerInternal<Long> txnManager;
+
+    /** Txn log manager */
+    private TxnLogManager<Long> txnLogManager;
+
+    /** Entry to be merged */
+    private Entry toUpdate;
+
+    /** Entry Id */
+    private Long updatedEntryId = new Long( 0 );
+
+    /** Entry to be added by a txn */
+    private Entry toAdd;
+
+    /** Entry Id */
+    private Long addedEntryId = new Long( 1 );
+
+    /** Entry to be added by a txn */
+    private Entry toDelete;
+
+    /** Entry Id */
+    private Long deletedEntryId = new Long( 2 );
+
+    /** Schema manager */
+    private SchemaManager schemaManager;
+
+    /** Sn attribute type */
+    private AttributeType SN_AT;
+
+    /** Gn attribute type */
+    private AttributeType GN_AT;
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+
+    /**
+     * Get the Log folder
+     */
+    private String getLogFolder() throws IOException
+    {
+        String file = folder.newFolder( LOG_SUFFIX ).getAbsolutePath();
+
+        return file;
+    }
+
+
+    @Before
+    @SuppressWarnings("unchecked")
+    public void setup() throws IOException, InvalidLogException
+    {
+        try
+        {
+            // Init the partition dn
+            dn = new Dn( "ou=department" );
+
+            schemaManager = new DefaultSchemaManager();
+            SN_AT = schemaManager.getAttributeType( "sn" );
+            GN_AT = schemaManager.getAttributeType( "gn" );
+
+            // Init the txn manager
+            TxnManagerFactory.<Long> init( LongComparator.INSTANCE, LongSerializer.INSTANCE, getLogFolder(),
+                logBufferSize, logFileSize );
+            txnManager = TxnManagerFactory.<Long> txnManagerInternalInstance();
+            txnLogManager = TxnManagerFactory.<Long> txnLogManagerInstance();
+
+            toUpdate = createEntry( updatedEntryId );
+            toAdd = createEntry( addedEntryId );
+            toDelete = createEntry( deletedEntryId );
+
+            // Begin a txn and do some entry changes.
+            DataChangeContainer<Long> changeContainer = new DataChangeContainer<Long>( dn );
+            changeContainer.setEntryID( updatedEntryId );
+            txnManager.beginTransaction( false );
+
+            Attribute attribute = new DefaultAttribute( "sn", SN_AT );
+            attribute.add( "test2" );
+
+            Modification redo = new DefaultModification( ModificationOperation.ADD_ATTRIBUTE, attribute );
+            Modification undo = new DefaultModification( ModificationOperation.REMOVE_ATTRIBUTE, attribute );
+            EntryChange<Long> eChange = new EntryChange<Long>( redo, undo );
+
+            changeContainer.getChanges().add( eChange );
+
+            txnLogManager.log( changeContainer, false );
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+
+            changeContainer = new DataChangeContainer<Long>( dn );
+            changeContainer.setEntryID( updatedEntryId );
+            attribute = new DefaultAttribute( "gn", GN_AT );
+            attribute.add( "test3" );
+
+            redo = new DefaultModification( ModificationOperation.ADD_ATTRIBUTE, attribute );
+            undo = new DefaultModification( ModificationOperation.REMOVE_ATTRIBUTE, attribute );
+            eChange = new EntryChange<Long>( redo, undo );
+
+            changeContainer.getChanges().add( eChange );
+            txnLogManager.log( changeContainer, false );
+
+            changeContainer = new DataChangeContainer<Long>( dn );
+            changeContainer.setEntryID( addedEntryId );
+            EntryAddDelete<Long> eAdd = new EntryAddDelete<Long>( toAdd, EntryAddDelete.Type.ADD );
+
+            changeContainer.getChanges().add( eAdd );
+            txnLogManager.log( changeContainer, false );
+
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+
+            changeContainer = new DataChangeContainer<Long>( dn );
+            changeContainer.setEntryID( deletedEntryId );
+            EntryAddDelete<Long> eDelete = new EntryAddDelete<Long>( toDelete, EntryAddDelete.Type.DELETE );
+
+            changeContainer.getChanges().add( eDelete );
+            txnLogManager.log( changeContainer, false );
+
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+
+    @After
+    public void teardown() throws IOException
+    {
+        try
+        {
+            txnManager.commitTransaction();
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+
+    @Test
+    public void testMergeModification()
+    {
+        try
+        {
+            Entry updated = txnLogManager.mergeUpdates( dn, updatedEntryId, toUpdate );
+
+            String value = updated.get( SN_AT ).getString();
+
+            assertTrue( value.equals( "test2" ) );
+
+            value = updated.get( GN_AT ).getString();
+
+            assertTrue( value.equals( "test3" ) );
+
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+
+    @Test
+    public void testMergeAdd()
+    {
+        try
+        {
+            Entry added = txnLogManager.mergeUpdates( dn, addedEntryId, null );
+
+            assertTrue( added == toAdd );
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+
+    @Test
+    public void testMergeDelete()
+    {
+        try
+        {
+            Entry deleted = txnLogManager.mergeUpdates( dn, deletedEntryId, toDelete );
+
+            assertTrue( deleted == null );
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+
+    private Entry createEntry( Long id ) throws Exception
+    {
+        String user = id.toString();
+
+        String dn = "cn=" + user + ",ou=department";
+
+        DefaultEntry entry = new DefaultEntry( schemaManager, dn,
+            "objectClass", "person",
+            "cn", user );
+
+        return entry;
+    }
+}

Added: directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/TxnConflicTest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/TxnConflicTest.java?rev=1196230&view=auto
==============================================================================
--- directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/TxnConflicTest.java (added)
+++ directory/apacheds/branches/apacheds-txns/core/src/test/java/org/apache/directory/server/core/txn/TxnConflicTest.java Tue Nov  1 19:29:19 2011
@@ -0,0 +1,176 @@
+package org.apache.directory.server.core.txn;
+
+
+import java.io.IOException;
+
+import org.apache.directory.server.core.log.InvalidLogException;
+import org.apache.directory.shared.ldap.model.message.SearchScope;
+import org.apache.directory.shared.ldap.model.name.Dn;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+public class TxnConflicTest
+{
+    /** Log buffer size : 4096 bytes */
+    private int logBufferSize = 1 << 12;
+
+    /** Log File Size : 8192 bytes */
+    private long logFileSize = 1 << 13;
+
+    /** log suffix */
+    private static String LOG_SUFFIX = "log";
+
+    /** Txn manager */
+    private TxnManagerInternal<Long> txnManager;
+
+    /** Txn log manager */
+    private TxnLogManager<Long> txnLogManager;
+
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
+
+    /**
+     * Get the Log folder
+     */
+    private String getLogFolder() throws IOException
+    {
+        String file = folder.newFolder( LOG_SUFFIX ).getAbsolutePath();
+
+        return file;
+    }
+
+
+    @Before
+    @SuppressWarnings("unchecked")
+    public void setup() throws IOException, InvalidLogException
+    {
+        try
+        {
+            // Init the txn manager
+            TxnManagerFactory.<Long> init( LongComparator.INSTANCE, LongSerializer.INSTANCE, getLogFolder(),
+                logBufferSize, logFileSize );
+            txnManager = TxnManagerFactory.<Long> txnManagerInternalInstance();
+            txnLogManager = TxnManagerFactory.<Long> txnLogManagerInstance();
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+
+    @Test
+    public void testExclusiveChangeConflict()
+    {
+        boolean conflicted;
+
+        try
+        {
+            Dn dn1 = new Dn( "cn=Test", "ou=department", "dc=example,dc=com" );
+            Dn dn2 = new Dn( "gn=Test1", "cn=Test", "ou=department", "dc=example,dc=com" );
+
+            ReadWriteTxn<Long> firstTxn;
+            ReadWriteTxn<Long> checkedTxn;
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addWrite( dn1, SearchScope.OBJECT );
+            firstTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addWrite( dn1, SearchScope.OBJECT );
+            checkedTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+
+            conflicted = checkedTxn.hasConflict( firstTxn );
+            assertTrue( conflicted == true );
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addRead( dn1, SearchScope.OBJECT );
+            checkedTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+
+            conflicted = checkedTxn.hasConflict( firstTxn );
+            assertTrue( conflicted == true );
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addWrite( dn2, SearchScope.OBJECT );
+            checkedTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+
+            conflicted = checkedTxn.hasConflict( firstTxn );
+            assertTrue( conflicted == false );
+            txnManager.commitTransaction();
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+
+    @Test
+    public void testSubtreeChangeConflict()
+    {
+        boolean conflicted;
+
+        try
+        {
+            Dn dn1 = new Dn( "cn=Test", "ou=department", "dc=example,dc=com" );
+            Dn dn2 = new Dn( "gn=Test1", "cn=Test", "ou=department", "dc=example,dc=com" );
+            Dn dn3 = new Dn( "ou=department", "dc=example,dc=com" );
+
+            ReadWriteTxn<Long> firstTxn;
+            ReadWriteTxn<Long> checkedTxn;
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addWrite( dn1, SearchScope.SUBTREE );
+            firstTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addRead( dn1, SearchScope.OBJECT );
+            checkedTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+
+            conflicted = checkedTxn.hasConflict( firstTxn );
+            assertTrue( conflicted == true );
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addWrite( dn2, SearchScope.OBJECT );
+            checkedTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+
+            conflicted = checkedTxn.hasConflict( firstTxn );
+            assertTrue( conflicted == true );
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addRead( dn1, SearchScope.SUBTREE );
+            checkedTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+
+            conflicted = checkedTxn.hasConflict( firstTxn );
+            assertTrue( conflicted == true );
+            txnManager.commitTransaction();
+
+            txnManager.beginTransaction( false );
+            txnLogManager.addWrite( dn3, SearchScope.OBJECT );
+            checkedTxn = ( ReadWriteTxn<Long> ) txnManager.getCurTxn();
+
+            conflicted = checkedTxn.hasConflict( firstTxn );
+            assertTrue( conflicted == false );
+            txnManager.commitTransaction();
+        }
+        catch ( Exception e )
+        {
+
+        }
+    }
+}