You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by el...@apache.org on 2011/03/16 18:50:11 UTC

svn commit: r1082234 - in /directory/shared/trunk/ldap/model/src: main/java/org/apache/directory/shared/ldap/model/ldif/ test/java/org/apache/directory/shared/ldap/model/ldif/

Author: elecharny
Date: Wed Mar 16 17:50:11 2011
New Revision: 1082234

URL: http://svn.apache.org/viewvc?rev=1082234&view=rev
Log:
o Added tests for LdifEntry class
o Fixed many errors in the Ldif parsing

Added:
    directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/ldif/LdifEntryTest.java
Modified:
    directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifEntry.java
    directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifReader.java

Modified: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifEntry.java
URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifEntry.java?rev=1082234&r1=1082233&r2=1082234&view=diff
==============================================================================
--- directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifEntry.java (original)
+++ directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifEntry.java Wed Mar 16 17:50:11 2011
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.directory.shared.i18n.I18n;
 import org.apache.directory.shared.ldap.model.entry.DefaultEntry;
 import org.apache.directory.shared.ldap.model.entry.DefaultEntryAttribute;
 import org.apache.directory.shared.ldap.model.entry.DefaultModification;
@@ -40,10 +41,14 @@ import org.apache.directory.shared.ldap.
 import org.apache.directory.shared.ldap.model.entry.StringValue;
 import org.apache.directory.shared.ldap.model.entry.Value;
 import org.apache.directory.shared.ldap.model.exception.LdapException;
+import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException;
 import org.apache.directory.shared.ldap.model.exception.LdapInvalidDnException;
 import org.apache.directory.shared.ldap.model.message.Control;
+import org.apache.directory.shared.ldap.model.message.ResultCodeEnum;
 import org.apache.directory.shared.ldap.model.name.Dn;
 import org.apache.directory.shared.ldap.model.name.Rdn;
+import org.apache.directory.shared.util.Base64;
+import org.apache.directory.shared.util.Strings;
 import org.apache.directory.shared.util.Unicode;
 
 
@@ -87,6 +92,9 @@ public class LdifEntry implements Clonea
     /** the entry */
     private Entry entry;
 
+    /** the DN */
+    private Dn entryDn;
+
     /** The controls */
     private Map<String, LdifControl> controls;
 
@@ -100,6 +108,7 @@ public class LdifEntry implements Clonea
         modificationList = new LinkedList<Modification>();
         modificationItems = new HashMap<String, Modification>();
         entry = new DefaultEntry( (Dn) null );
+        entryDn = null;
         controls = null;
     }
 
@@ -113,8 +122,148 @@ public class LdifEntry implements Clonea
         modificationList = new LinkedList<Modification>();
         modificationItems = new HashMap<String, Modification>();
         this.entry = entry;
+        entryDn = entry.getDn();
         controls = null;
     }
+    
+    
+    /**
+     * Creates a LdifEntry using a list of strings representing the Ldif element
+     * 
+     * @param dn The LdifEntry DN
+     * @param avas The Ldif to convert to an LdifEntry
+     */
+    public LdifEntry( Dn dn, Object... avas ) throws LdapInvalidAttributeValueException, LdapLdifException
+    {
+        // First, convert the arguments to a full LDIF
+        StringBuilder sb = new StringBuilder();
+        int pos = 0;
+        boolean valueExpected = false;
+        String dnStr = null;
+        
+        if ( dn == null )
+        {
+            dnStr = "";
+        }
+        else
+        {
+            dnStr = dn.getName();
+        }
+        
+        if ( LdifUtils.isLDIFSafe( dnStr ) )
+        {
+            sb.append( "dn: " ).append( dnStr ).append( '\n' );
+        }
+        else
+        {
+            sb.append( "dn:: " ).append( Base64.encode( Strings.getBytesUtf8( dnStr ) ) ).append( '\n' );
+        }
+        
+        for ( Object ava : avas )
+        {
+            if ( !valueExpected )
+            {
+                if ( !( ava instanceof String ) )
+                {
+                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
+                        I18n.ERR_12085, ( pos + 1 ) ) );
+                }
+
+                String attribute = ( String ) ava;
+                sb.append( attribute );
+
+                if ( attribute.indexOf( ':' ) != -1 )
+                {
+                    sb.append( '\n' );
+                }
+                else
+                {
+                    valueExpected = true;
+                }
+            }
+            else
+            {
+                if ( ava instanceof String )
+                {
+                    sb.append( ": " ).append( ( String ) ava ).append( '\n' );
+                }
+                else if ( ava instanceof byte[] )
+                {
+                    sb.append( ":: " );
+                    sb.append( new String( Base64.encode( ( byte[] ) ava ) ) );
+                    sb.append( '\n' );
+                }
+                else
+                {
+                    throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err(
+                        I18n.ERR_12086, ( pos + 1 ) ) );
+                }
+
+                valueExpected = false;
+            }
+        }
+
+        if ( valueExpected )
+        {
+            throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n
+                .err( I18n.ERR_12087 ) );
+        }
+
+        // Now, parse the Ldif and convert it to a LdifEntry
+        LdifReader reader = new LdifReader();
+        List<LdifEntry> ldifEntries = reader.parseLdif( sb.toString() );
+
+        if ( ( ldifEntries != null ) && ( ldifEntries.size() == 1 ) )
+        {
+            LdifEntry ldifEntry = ldifEntries.get( 0 );
+            
+            changeType = ldifEntry.getChangeType();
+            controls = ldifEntry.getControls();
+            entryDn = ldifEntry.getDn();
+            
+            switch ( ldifEntry.getChangeType() )
+            {
+                case Add :
+                    // Fallback
+                case None :
+                    entry = ldifEntry.getEntry();
+                    break;
+                    
+                case Delete :
+                    break;
+                    
+                case ModDn :
+                case ModRdn :
+                    newRdn = ldifEntry.getNewRdn();
+                    newSuperior = ldifEntry.getNewSuperior();
+                    deleteOldRdn = ldifEntry.isDeleteOldRdn();
+                    break;
+                    
+                case Modify :
+                    modificationList = ldifEntry.getModificationItems();
+                    modificationItems = new HashMap<String, Modification>();
+                    
+                    for ( Modification modification : modificationList )
+                    {
+                        modificationItems.put( modification.getAttribute().getId(), modification );
+                    }
+                    break;
+            }
+        }
+    }
+    
+    
+    /**
+     * Creates a LdifEntry using a list of strings representing the Ldif element
+     * 
+     * @param dn The LdifEntry DN
+     * @param avas The Ldif to convert to an LdifEntry
+     */
+    public LdifEntry( String dn, Object... strings ) 
+        throws LdapInvalidAttributeValueException, LdapLdifException, LdapInvalidDnException
+    {
+        this( new Dn( dn ), strings );
+    }
 
 
     /**
@@ -124,6 +273,7 @@ public class LdifEntry implements Clonea
      */
     public void setDn( Dn dn )
     {
+        entryDn = dn;
         entry.setDn( dn );
     }
 
@@ -136,7 +286,8 @@ public class LdifEntry implements Clonea
      */
     public void setDn( String dn ) throws LdapInvalidDnException
     {
-        entry.setDn( new Dn( dn ) );
+        entryDn = new Dn( dn );
+        entry.setDn( entryDn );
     }
 
 
@@ -383,7 +534,7 @@ public class LdifEntry implements Clonea
      */
     public Dn getDn()
     {
-        return entry.getDn();
+        return entryDn;
     }
 
 

Modified: directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifReader.java
URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifReader.java?rev=1082234&r1=1082233&r2=1082234&view=diff
==============================================================================
--- directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifReader.java (original)
+++ directory/shared/trunk/ldap/model/src/main/java/org/apache/directory/shared/ldap/model/ldif/LdifReader.java Wed Mar 16 17:50:11 2011
@@ -43,19 +43,19 @@ import java.util.NoSuchElementException;
 
 import org.apache.directory.shared.asn1.util.OID;
 import org.apache.directory.shared.i18n.I18n;
+import org.apache.directory.shared.ldap.model.entry.DefaultEntryAttribute;
 import org.apache.directory.shared.ldap.model.entry.EntryAttribute;
 import org.apache.directory.shared.ldap.model.entry.ModificationOperation;
+import org.apache.directory.shared.ldap.model.exception.LdapException;
 import org.apache.directory.shared.ldap.model.exception.LdapInvalidDnException;
 import org.apache.directory.shared.ldap.model.message.Control;
 import org.apache.directory.shared.ldap.model.name.Dn;
 import org.apache.directory.shared.ldap.model.name.DnParser;
 import org.apache.directory.shared.ldap.model.name.Rdn;
-import org.apache.directory.shared.util.exception.NotImplementedException;
-import org.apache.directory.shared.ldap.model.entry.DefaultEntryAttribute;
-import org.apache.directory.shared.ldap.model.exception.LdapException;
 import org.apache.directory.shared.util.Base64;
 import org.apache.directory.shared.util.Chars;
 import org.apache.directory.shared.util.Strings;
+import org.apache.directory.shared.util.exception.NotImplementedException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -812,7 +812,16 @@ public class LdifReader implements Itera
             if ( Chars.isCharASCII(controlValue, criticalPos + 1, ':') )
             {
                 // Base 64 encoded value
-                byte[] value = Base64.decode( line.substring( criticalPos + 2 ).toCharArray() );
+                
+                // Skip the <fill>
+                pos = criticalPos + 2;
+                
+                while ( Chars.isCharASCII(controlValue, pos, ' ') )
+                {
+                    pos++;
+                }
+
+                byte[] value = Base64.decode( line.substring( pos ).toCharArray() );
                 control.setValue( value );
             }
             else if ( Chars.isCharASCII(controlValue, criticalPos + 1, '<') )
@@ -822,12 +831,20 @@ public class LdifReader implements Itera
             }
             else
             {
+                // Skip the <fill>
+                pos = criticalPos + 1;
+                
+                while ( Chars.isCharASCII(controlValue, pos, ' ') )
+                {
+                    pos++;
+                }
+
                 // Standard value
-                byte[] value = new byte[length - criticalPos - 1];
+                byte[] value = new byte[length - pos];
 
-                for ( int i = 0; i < length - criticalPos - 1; i++ )
+                for ( int i = 0; i < length - pos; i++ )
                 {
-                    value[i] = ( byte ) controlValue[i + criticalPos + 1];
+                    value[i] = ( byte ) controlValue[i + pos];
                 }
 
                 control.setValue( value );
@@ -1001,7 +1018,7 @@ public class LdifReader implements Itera
 
             if ( lowerLine.startsWith( "-" ) )
             {
-                if ( state != ATTRVAL_SPEC_OR_SEP )
+                if ( ( state != ATTRVAL_SPEC_OR_SEP ) && ( state != ATTRVAL_SPEC ) ) 
                 {
                     LOG.error( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) );
                     throw new LdapLdifException( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) );
@@ -1031,7 +1048,7 @@ public class LdifReader implements Itera
                     throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
                 }
 
-                modified = Strings.trim(line.substring("add:".length()));
+                modified = Strings.trim( line.substring( "add:".length() ) );
                 modificationType = ModificationOperation.ADD_ATTRIBUTE;
                 attribute = new DefaultEntryAttribute( modified );
 
@@ -1107,6 +1124,12 @@ public class LdifReader implements Itera
                 state = ATTRVAL_SPEC_OR_SEP;
             }
         }
+
+        if ( state != MOD_SPEC )
+        {
+            LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
+            throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
+        }
     }
 
 
@@ -1148,14 +1171,14 @@ public class LdifReader implements Itera
         // The changetype and operation has already been parsed.
         entry.setChangeType( operation );
 
-        switch ( operation.getChangeType() )
+        switch ( operation )
         {
-            case ChangeType.DELETE_ORDINAL:
+            case Delete:
                 // The change type will tell that it's a delete operation,
                 // the dn is used as a key.
                 return;
 
-            case ChangeType.ADD_ORDINAL:
+            case Add:
                 // We will iterate through all attribute/value pairs
                 while ( iter.hasNext() )
                 {
@@ -1166,16 +1189,16 @@ public class LdifReader implements Itera
 
                 return;
 
-            case ChangeType.MODIFY_ORDINAL:
+            case Modify:
                 parseModify( entry, iter );
                 return;
 
-            case ChangeType.MODRDN_ORDINAL:// They are supposed to have the same syntax ???
-            case ChangeType.MODDN_ORDINAL:
+            case ModDn:// They are supposed to have the same syntax ???
+            case ModRdn:
                 // First, parse the modrdn part
                 parseModRdn( entry, iter );
 
-                // The next line should be the new superior
+                // The next line should be the new superior, if we have one
                 if ( iter.hasNext() )
                 {
                     String line = iter.next();
@@ -1204,14 +1227,6 @@ public class LdifReader implements Itera
                         }
                     }
                 }
-                else
-                {
-                    if ( operation == ChangeType.ModDn )
-                    {
-                        LOG.error( I18n.err( I18n.ERR_12046 ) );
-                        throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) );
-                    }
-                }
 
                 return;
 

Added: directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/ldif/LdifEntryTest.java
URL: http://svn.apache.org/viewvc/directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/ldif/LdifEntryTest.java?rev=1082234&view=auto
==============================================================================
--- directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/ldif/LdifEntryTest.java (added)
+++ directory/shared/trunk/ldap/model/src/test/java/org/apache/directory/shared/ldap/model/ldif/LdifEntryTest.java Wed Mar 16 17:50:11 2011
@@ -0,0 +1,694 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.directory.shared.ldap.model.ldif;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.directory.shared.ldap.model.entry.Entry;
+import org.apache.directory.shared.ldap.model.entry.EntryAttribute;
+import org.apache.directory.shared.ldap.model.entry.Modification;
+import org.apache.directory.shared.ldap.model.entry.ModificationOperation;
+import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException;
+import org.apache.directory.shared.ldap.model.name.Dn;
+import org.apache.directory.shared.util.Strings;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.mycila.junit.concurrent.Concurrency;
+import com.mycila.junit.concurrent.ConcurrentJunitRunner;
+
+
+/**
+ * Test the LdifEntry class
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ */
+@RunWith(ConcurrentJunitRunner.class)
+@Concurrency()
+public class LdifEntryTest
+{
+    /**
+     * Check that we can't create an empty LdifEntry
+     */
+    @Test( expected=LdapInvalidAttributeValueException.class)
+    public void testLdifEntryEmpty() throws Exception
+    {
+        new LdifEntry( "", "" );
+    }
+
+
+    /**
+     * Check that we can create an LdifEntry with an Empty Dn
+     */
+    @Test
+    public void testLdifEntryEmptyDn() throws Exception
+    {
+        Entry entry = LdifUtils.createEntry( "", "cn: test" );
+        LdifEntry ldifEntry = new LdifEntry( "", "cn: test" );
+        
+        assertNotNull( ldifEntry );
+        assertEquals( Dn.EMPTY_DN, ldifEntry.getDn() );
+        assertEquals( ChangeType.None, ldifEntry.getChangeType() );
+        assertEquals( entry, ldifEntry.getEntry() );
+    }
+
+
+    /**
+     * Check that we can create an LdifEntry with a null Dn
+     */
+    @Test
+    public void testLdifEntryNullDn() throws Exception
+    {
+        Entry entry = LdifUtils.createEntry( "", "cn: test" );
+        LdifEntry ldifEntry = new LdifEntry( (Dn)null, "cn: test" );
+        
+        assertNotNull( ldifEntry );
+        assertEquals( Dn.EMPTY_DN, ldifEntry.getDn() );
+        assertEquals( ChangeType.None, ldifEntry.getChangeType() );
+        assertEquals( entry, ldifEntry.getEntry() );
+    }
+
+
+    /**
+     * Test a simple LdifEntry
+     * @throws Exception
+     */
+    @Test
+    public void testSimpleLdifEntry() throws Exception
+    {
+        String ldif = 
+            "cn: app1\n" + 
+            "objectClass: top\n" + 
+            "objectClass: apApplication\n" + 
+            "displayName:   app1   \n" +
+            "dependencies:\n" + 
+            "envVars:";
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertTrue( ldifEntry.isLdifContent() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+
+        EntryAttribute attr = ldifEntry.get( "displayname" );
+        assertTrue( attr.contains( "app1" ) );
+    }
+    
+    
+    /**
+     * Test a Delete changeType LdifEntry with no control
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testLdifParserChangeTypeDeleteNoControl() throws Exception
+    {
+        String ldif = 
+            "# Delete an entry. The operation will attach the LDAPv3\n" +
+            "# Tree Delete Control defined in [9]. The criticality\n" +
+            "# field is \"true\" and the controlValue field is\n" + 
+            "# absent, as required by [9].\n" +
+            "changetype: delete\n";
+
+        LdifEntry ldifEntry = new LdifEntry( "ou=Product Development, dc=airius, dc=com", ldif );
+        
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Delete, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "ou=Product Development, dc=airius, dc=com", ldifEntry.getDn().getName() );
+        assertFalse( ldifEntry.hasControls() );
+    }
+    
+    
+    /**
+     * Test a Delete changeType LdifEntry with no control and following Attrs :
+     * should get an exception
+     * 
+     * @throws Exception
+     */
+    @Test( expected = LdapLdifException.class )
+    public void testLdifParserChangeTypeDeleteNoControlAttribute() throws Exception
+    {
+        String ldif = 
+            "# Delete an entry. The operation will attach the LDAPv3\n" +
+            "# Tree Delete Control defined in [9]. The criticality\n" +
+            "# field is \"true\" and the controlValue field is\n" + 
+            "# absent, as required by [9].\n" +
+            "changetype: delete\n"+
+            "cn: bad !!\n";
+
+        new LdifEntry( "ou=Product Development, dc=airius, dc=com", ldif );
+    }
+
+
+    /**
+     * Test a Delete changeType LdifEntry with one control
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testLdifParserChangeTypeDeleteWithControl() throws Exception
+    {
+        String ldif = 
+            "# Delete an entry. The operation will attach the LDAPv3\n" +
+            "# Tree Delete Control defined in [9]. The criticality\n" +
+            "# field is \"true\" and the controlValue field is\n" + 
+            "# absent, as required by [9].\n" +
+            "control: 1.2.840.113556.1.4.805 true\n" +
+            "changetype: delete\n";
+
+        LdifEntry ldifEntry = new LdifEntry( "ou=Product Development, dc=airius, dc=com", ldif );
+        
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Delete, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "ou=Product Development, dc=airius, dc=com", ldifEntry.getDn().getName() );
+        assertTrue( ldifEntry.hasControls() );
+        
+        LdifControl ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.805" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.805", ldifControl.getOid() );
+        assertTrue( ldifControl.isCritical() );
+        assertNull( ldifControl.getValue() );
+    }
+    
+
+    /**
+     * Test a Delete changeType LdifEntry with controls
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void testLdifParserChangeTypeDeleteWithControls() throws Exception
+    {
+        String ldif = 
+            "# Delete an entry. The operation will attach the LDAPv3\n" +
+            "# Tree Delete Control defined in [9]. The criticality\n" +
+            "# field is \"true\" and the controlValue field is\n" + 
+            "# absent, as required by [9].\n" +
+            "control: 1.2.840.113556.1.4.805 true\n" +
+            "control: 1.2.840.113556.1.4.806 false: test\n" +
+            "changetype: delete\n";
+
+        LdifEntry ldifEntry = new LdifEntry( "ou=Product Development, dc=airius, dc=com", ldif );
+        
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Delete, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "ou=Product Development, dc=airius, dc=com", ldifEntry.getDn().getName() );
+        assertTrue( ldifEntry.hasControls() );
+        
+        LdifControl ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.805" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.805", ldifControl.getOid() );
+        assertTrue( ldifControl.isCritical() );
+        assertNull( ldifControl.getValue() );
+        
+        ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.806" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.806", ldifControl.getOid() );
+        assertFalse( ldifControl.isCritical() );
+        assertNotNull( ldifControl.getValue() );
+        assertEquals( "test", Strings.utf8ToString( ldifControl.getValue() ) );
+    }
+
+
+    /**
+     * Test a Add changeType LdifEntry with no control
+     * @throws Exception
+     */
+    @Test
+    public void testLdifEntryChangeTypeAddNoControl() throws Exception
+    {
+        String ldif = 
+            "changetype: add\n" +
+            "cn: app1\n" + 
+            "objectClass: top\n" + 
+            "objectClass: apApplication\n" + 
+            "displayName:   app1   \n" +
+            "dependencies:\n" + 
+            "envVars:";
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Add, ldifEntry.getChangeType() );
+        assertNotNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertFalse( ldifEntry.hasControls() );
+        assertTrue( ldifEntry.isLdifChange() );
+
+        EntryAttribute attr = ldifEntry.get( "displayname" );
+        assertTrue( attr.contains( "app1" ) );
+    }
+    
+    
+    /**
+     * Test a Add changeType LdifEntry with a control
+     * @throws Exception
+     */
+    @Test
+    public void testLdifEntryChangeTypeAddWithControl() throws Exception
+    {
+        String ldif = 
+            "control: 1.2.840.113556.1.4.805 true\n" +
+            "changetype: add\n" +
+            "cn: app1\n" + 
+            "objectClass: top\n" + 
+            "objectClass: apApplication\n" + 
+            "displayName:   app1   \n" +
+            "dependencies:\n" + 
+            "envVars:";
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Add, ldifEntry.getChangeType() );
+        assertNotNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertTrue( ldifEntry.isLdifChange() );
+
+        EntryAttribute attr = ldifEntry.get( "displayname" );
+        assertTrue( attr.contains( "app1" ) );
+        assertTrue( ldifEntry.hasControls() );
+        
+        LdifControl ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.805" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.805", ldifControl.getOid() );
+        assertTrue( ldifControl.isCritical() );
+        assertNull( ldifControl.getValue() );
+    }
+    
+    
+    /**
+     * Test a Add changeType LdifEntry with controls
+     * @throws Exception
+     */
+    @Test
+    public void testLdifEntryChangeTypeAddWithControls() throws Exception
+    {
+        String ldif = 
+            "control: 1.2.840.113556.1.4.805 true\n" +
+            "control: 1.2.840.113556.1.4.806 false: test\n" +
+            "changetype: add\n" +
+            "cn: app1\n" + 
+            "objectClass: top\n" + 
+            "objectClass: apApplication\n" + 
+            "displayName:   app1   \n" +
+            "dependencies:\n" + 
+            "envVars:";
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Add, ldifEntry.getChangeType() );
+        assertNotNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertTrue( ldifEntry.isLdifChange() );
+
+        EntryAttribute attr = ldifEntry.get( "displayname" );
+        assertTrue( attr.contains( "app1" ) );
+        assertTrue( ldifEntry.hasControls() );
+        
+        LdifControl ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.805" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.805", ldifControl.getOid() );
+        assertTrue( ldifControl.isCritical() );
+        assertNull( ldifControl.getValue() );
+        
+        ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.806" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.806", ldifControl.getOid() );
+        assertFalse( ldifControl.isCritical() );
+        assertNotNull( ldifControl.getValue() );
+        assertEquals( "test", Strings.utf8ToString( ldifControl.getValue() ) );
+    }
+
+
+    /**
+     * Test a ModDn changeType LdifEntry with no control
+     */
+    @Test
+    public void testLdifEntryChangeTypeModDnNoControl() throws Exception
+    {
+        String ldif = 
+            "changetype: moddn\n" +
+            "newrdn: cn=app2\n" + 
+            "deleteoldrdn: 1\n"; 
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.ModDn, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertFalse( ldifEntry.hasControls() );
+        assertTrue( ldifEntry.isLdifChange() );
+        assertEquals( "cn=app2", ldifEntry.getNewRdn() );
+        assertTrue( ldifEntry.isDeleteOldRdn() );
+        assertNull( ldifEntry.getNewSuperior() );
+    }
+    
+    
+    /**
+     * Test a ModDn changeType LdifEntry with no newRdn
+     */
+    @Test( expected = LdapLdifException.class )
+    public void testLdifEntryChangeTypeModDnNoNewRdn() throws Exception
+    {
+        String ldif = 
+            "changetype: moddn\n" +
+            "deleteoldrdn: 1\n"; 
+
+        new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+    }
+
+
+    /**
+     * Test a ModDn changeType LdifEntry with no deleteOldRdn flag
+     */
+    @Test( expected = LdapLdifException.class )
+    public void testLdifEntryChangeTypeModDnNoDeleteOldRdn() throws Exception
+    {
+        String ldif = 
+            "changetype: moddn\n" +
+            "newrdn: cn=app2\n"; 
+
+        new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+    }
+
+
+    /**
+     * Test a ModDn changeType LdifEntry with no control and a newSuperior
+     */
+    @Test
+    public void testLdifEntryChangeTypeModDnRenameNoControlNewSuperior() throws Exception
+    {
+        String ldif = 
+            "changetype: moddn\n" +
+            "newrdn: cn=app2\n" + 
+            "deleteoldrdn: 1\n" +
+            "newsuperior: dc=example, dc=com"; 
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.ModDn, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertFalse( ldifEntry.hasControls() );
+        assertTrue( ldifEntry.isLdifChange() );
+        assertEquals( "cn=app2", ldifEntry.getNewRdn() );
+        assertTrue( ldifEntry.isDeleteOldRdn() );
+        assertEquals( "dc=example, dc=com", ldifEntry.getNewSuperior() );
+    }
+
+    
+    /**
+     * Test a ModDn changeType LdifEntry with a control
+     * @throws Exception
+     */
+    @Test
+    public void testLdifEntryChangeTypeModdnWithControl() throws Exception
+    {
+        String ldif = 
+            "control: 1.2.840.113556.1.4.805 true\n" +
+            "changetype: moddn\n" +
+            "newrdn: cn=app2\n" + 
+            "deleteoldrdn: 1\n"; 
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.ModDn, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertTrue( ldifEntry.isLdifChange() );
+        assertEquals( "cn=app2", ldifEntry.getNewRdn() );
+        assertNull( ldifEntry.getNewSuperior() );
+        assertTrue( ldifEntry.isDeleteOldRdn() );
+
+        assertTrue( ldifEntry.hasControls() );
+        
+        LdifControl ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.805" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.805", ldifControl.getOid() );
+        assertTrue( ldifControl.isCritical() );
+        assertNull( ldifControl.getValue() );
+    }
+    
+    
+    /**
+     * Test a ModDN changeType LdifEntry with controls
+     * @throws Exception
+     */
+    @Test
+    public void testLdifEntryChangeTypeModddnWithControls() throws Exception
+    {
+        String ldif = 
+            "control: 1.2.840.113556.1.4.805 true\n" +
+            "control: 1.2.840.113556.1.4.806 false: test\n" +
+            "changetype: moddn\n" +
+            "newrdn: cn=app2\n" + 
+            "deleteoldrdn: 1\n"; 
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.ModDn, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertTrue( ldifEntry.isLdifChange() );
+        assertEquals( "cn=app2", ldifEntry.getNewRdn() );
+        assertTrue( ldifEntry.isDeleteOldRdn() );
+        assertNull( ldifEntry.getNewSuperior() );
+        assertTrue( ldifEntry.hasControls() );
+        
+        LdifControl ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.805" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.805", ldifControl.getOid() );
+        assertTrue( ldifControl.isCritical() );
+        assertNull( ldifControl.getValue() );
+        
+        ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.806" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.806", ldifControl.getOid() );
+        assertFalse( ldifControl.isCritical() );
+        assertNotNull( ldifControl.getValue() );
+        assertEquals( "test", Strings.utf8ToString( ldifControl.getValue() ) );
+    }
+    
+    
+    /**
+     * Test a Modify changeType LdifEntry with no control
+     */
+    @Test
+    public void testLdifEntryChangeTypeModifySimple() throws Exception
+    {
+        String ldif = 
+            "changetype: modify\n" +
+            "add: cn\n" +
+            "cn: v1\n" + 
+            "cn: v2\n" +
+            "-"; 
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Modify, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertFalse( ldifEntry.hasControls() );
+        assertTrue( ldifEntry.isLdifChange() );
+        
+        // Check the modification
+        assertNotNull( ldifEntry.getModificationItems() );
+        
+        for ( Modification modification : ldifEntry.getModificationItems() )
+        {
+            assertEquals( ModificationOperation.ADD_ATTRIBUTE, modification.getOperation() );
+            EntryAttribute attribute = modification.getAttribute();
+
+            assertNotNull( attribute );
+            assertEquals( "cn", attribute.getId() );
+            assertTrue( attribute.contains( "v1", "v2" ) );
+            
+        }
+    }
+    
+    
+    /**
+     * Test a Modify changeType LdifEntry with no end separator ("-")
+     */
+    @Test( expected = LdapLdifException.class )
+    public void testLdifEntryChangeTypeModifyNoEndSeparator() throws Exception
+    {
+        String ldif = 
+            "changetype: modify\n" +
+            "add: cn\n" +
+            "cn: v1\n" + 
+            "cn: v2\n";
+
+        new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+    }
+
+
+    /**
+     * Test a Modify changeType LdifEntry with no operation
+     */
+    @Test( expected = LdapLdifException.class )
+    public void testLdifEntryChangeTypeModifyNoOperator() throws Exception
+    {
+        String ldif = 
+            "changetype: modify\n" +
+            "cn: v1\n" + 
+            "dn: v2\n" +
+            "-";
+
+        new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+    }
+
+
+    /**
+     * Test a Modify changeType LdifEntry with no attributes
+     */
+    @Test
+    public void testLdifEntryChangeTypeModifyNoAttribute() throws Exception
+    {
+        String ldif = 
+            "changetype: modify\n" +
+            "add: cn\n" +
+            "-"; 
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Modify, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertFalse( ldifEntry.hasControls() );
+        assertTrue( ldifEntry.isLdifChange() );
+        
+        // Check the modification
+        assertNotNull( ldifEntry.getModificationItems() );
+        
+        for ( Modification modification : ldifEntry.getModificationItems() )
+        {
+            assertEquals( ModificationOperation.ADD_ATTRIBUTE, modification.getOperation() );
+            EntryAttribute attribute = modification.getAttribute();
+
+            assertNotNull( attribute );
+            assertEquals( "cn", attribute.getId() );
+            assertNotNull( attribute.get() );
+            assertTrue( attribute.get().isNull() );
+        }
+    }
+    
+    
+    /**
+     * Test a Modify changeType LdifEntry with a different attribute used
+     */
+    @Test( expected = LdapLdifException.class )
+    public void testLdifEntryChangeTypeModifyNotSameAttr() throws Exception
+    {
+        String ldif = 
+            "changetype: modify\n" +
+            "add: cn\n" +
+            "sn: v1\n" + 
+            "sn: v2\n" +
+            "-"; 
+
+        new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+    }
+    
+    
+    /**
+     * Test a Modify changeType LdifEntry with a different attribute used
+     */
+    @Test( expected = LdapLdifException.class )
+    public void testLdifEntryChangeTypeModifyNotSameAttr2() throws Exception
+    {
+        String ldif = 
+            "changetype: modify\n" +
+            "add: cn\n" +
+            "cn: v1\n" + 
+            "sn: v2\n" +
+            "-"; 
+
+        new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+    }
+    
+    
+    /**
+     * Test a Modify changeType LdifEntry with no attributes and controls
+     */
+    @Test
+    public void testLdifEntryChangeTypeModifyNoAttributeWithControls() throws Exception
+    {
+        String ldif = 
+            "control: 1.2.840.113556.1.4.805 true\n" +
+            "control: 1.2.840.113556.1.4.806 false: test\n" +
+            "changetype: modify\n" +
+            "add: cn\n" +
+            "-"; 
+
+        LdifEntry ldifEntry = new LdifEntry( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldif );
+
+        assertNotNull( ldifEntry );
+        assertEquals( ChangeType.Modify, ldifEntry.getChangeType() );
+        assertNull( ldifEntry.getEntry() );
+        assertEquals( "cn=app1,ou=applications,ou=conf,dc=apache,dc=org", ldifEntry.getDn().getName() );
+        assertTrue( ldifEntry.isLdifChange() );
+        
+        // Check the modification
+        assertNotNull( ldifEntry.getModificationItems() );
+        
+        for ( Modification modification : ldifEntry.getModificationItems() )
+        {
+            assertEquals( ModificationOperation.ADD_ATTRIBUTE, modification.getOperation() );
+            EntryAttribute attribute = modification.getAttribute();
+
+            assertNotNull( attribute );
+            assertEquals( "cn", attribute.getId() );
+            assertEquals( 1, attribute.size() );
+            assertTrue(  attribute.get().isNull() );
+        }
+        
+        assertTrue( ldifEntry.hasControls() );
+        
+        LdifControl ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.805" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.805", ldifControl.getOid() );
+        assertTrue( ldifControl.isCritical() );
+        assertNull( ldifControl.getValue() );
+        
+        ldifControl = ldifEntry.getControl( "1.2.840.113556.1.4.806" );
+        assertNotNull( ldifControl );
+        assertEquals( "1.2.840.113556.1.4.806", ldifControl.getOid() );
+        assertFalse( ldifControl.isCritical() );
+        assertNotNull( ldifControl.getValue() );
+        assertEquals( "test", Strings.utf8ToString( ldifControl.getValue() ) );
+    }
+}