directory-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From akaras...@apache.org
Subject svn commit: r375785 [17/28] - in /directory/trunks: apacheds/ apacheds/core-plugin/src/main/antlr/ apacheds/core-plugin/src/main/java/org/apache/directory/ apacheds/core-plugin/src/main/java/org/apache/directory/server/ apacheds/core-plugin/src/main/ja...
Date Wed, 08 Feb 2006 00:18:49 GMT
Added: directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/MiscTest.java
URL: http://svn.apache.org/viewcvs/directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/MiscTest.java?rev=375785&view=auto
==============================================================================
--- directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/MiscTest.java (added)
+++ directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/MiscTest.java Tue Feb  7 16:10:02 2006
@@ -0,0 +1,415 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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.server;
+
+
+import org.apache.directory.server.core.configuration.MutableDirectoryPartitionConfiguration;
+import org.apache.directory.server.unit.AbstractServerTest;
+import org.apache.directory.shared.asn1.util.Asn1StringUtils;
+import org.apache.directory.shared.ldap.message.Control;
+import org.apache.directory.shared.ldap.util.ArrayUtils;
+import org.apache.directory.shared.ldap.util.EmptyEnumeration;
+
+import java.util.Hashtable;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
+
+import javax.naming.Context;
+import javax.naming.NoPermissionException;
+import javax.naming.NamingEnumeration;
+import javax.naming.OperationNotSupportedException;
+import javax.naming.directory.*;
+import javax.naming.ldap.InitialLdapContext;
+
+
+/**
+ * A set of miscellanous tests.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev: 375632 $
+ */
+public class MiscTest extends AbstractServerTest
+{
+    /**
+     * Cleans up old database files on creation.
+     */
+    public MiscTest()
+    {
+    }
+
+
+    /**
+     * Customizes setup for each test case.
+     *
+     * @throws Exception
+     */
+    public void setUp() throws Exception
+    {
+        if ( this.getName().equals( "testDisableAnonymousBinds" ) )
+        {
+            configuration.setAllowAnonymousAccess( false );
+        }
+        else if ( this.getName().equals( "testEnableAnonymousBindsOnRootDSE" ) )
+        {
+            configuration.setAllowAnonymousAccess( false );
+        }
+        else if ( this.getName().equals( "testUserAuthOnMixedCaseSuffix" ) )
+        {
+            Set partitions = new HashSet();
+            partitions.addAll( configuration.getContextPartitionConfigurations() );
+            MutableDirectoryPartitionConfiguration partition = new MutableDirectoryPartitionConfiguration();
+            partition.setSuffix( "dc=aPache,dc=org" );
+            Attributes entry = new BasicAttributes( "dc", "aPache", true );
+            Attribute oc = new BasicAttribute( "objectClass" );
+            entry.put( oc );
+            oc.add( "top" );
+            oc.add( "domain" );
+            partition.setName( "apache" );
+            partition.setContextEntry( entry );
+            partition.setIndexedAttributes( Collections.singleton( "dc" ) );
+            partitions.add( partition );
+            configuration.setContextPartitionConfigurations( partitions );
+	}
+        else if ( this.getName().equals( "testAnonymousBindsEnabledBaseSearch" ) )
+        {
+            // allow anonymous access
+            configuration.setAllowAnonymousAccess( true );
+            
+            // create a partition to search
+            Set partitions = new HashSet();
+            partitions.addAll( configuration.getContextPartitionConfigurations() );
+            MutableDirectoryPartitionConfiguration partition = new MutableDirectoryPartitionConfiguration();
+            partition.setSuffix( "dc=apache,dc=org" );
+            Attributes entry = new BasicAttributes( "dc", "apache", true );
+            Attribute oc = new BasicAttribute( "objectClass" );
+            entry.put( oc );
+            oc.add( "top" );
+            oc.add( "domain" );
+            partition.setName( "apache" );
+            partition.setContextEntry( entry );
+            partition.setIndexedAttributes( Collections.singleton( "dc" ) );
+            partitions.add( partition );
+            configuration.setContextPartitionConfigurations( partitions );
+        }
+ 
+
+        super.setUp();
+    }
+
+
+    /**
+     * Test to make sure anonymous binds are disabled when going through
+     * the wire protocol.
+     *
+     * @throws Exception if anything goes wrong
+     */
+    public void testDisableAnonymousBinds() throws Exception
+    {
+        // Use the SUN JNDI provider to hit server port and bind as anonymous
+        InitialDirContext ic = null;
+        final Hashtable env = new Hashtable();
+
+        env.put( Context.PROVIDER_URL, "ldap://localhost:" + port + "/ou=system" );
+        env.put( Context.SECURITY_AUTHENTICATION, "none" );
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+
+        boolean connected = false;
+        while( ! connected )
+        {
+            try
+            {
+                ic = new InitialDirContext( env );
+                connected = true;
+            }
+            catch( Exception e )
+            {
+            }
+        }
+        
+        try
+        {
+            ic.search( "", "(objectClass=*)", new SearchControls() );
+            fail( "If anonymous binds are disabled we should never get here!" );
+        }
+        catch ( NoPermissionException e )
+        {
+        }
+
+        Attributes attrs = new BasicAttributes( true );
+        Attribute oc = new BasicAttribute( "objectClass" );
+        attrs.put( oc );
+        oc.add( "top" );
+        oc.add( "organizationalUnit" );
+
+        try
+        {
+            ic.createSubcontext( "ou=blah", attrs );
+        }
+        catch( NoPermissionException e )
+        {
+        }
+    }
+
+
+    /**
+     * Test to make sure anonymous binds are allowed on the RootDSE even when disabled
+     * in general when going through the wire protocol.
+     *
+     * @throws Exception if anything goes wrong
+     */
+    public void testEnableAnonymousBindsOnRootDSE() throws Exception
+    {
+        // Use the SUN JNDI provider to hit server port and bind as anonymous
+
+        final Hashtable env = new Hashtable();
+
+        env.put( Context.PROVIDER_URL, "ldap://localhost:" + port + "/" );
+        env.put( Context.SECURITY_AUTHENTICATION, "none" );
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+
+        InitialDirContext ctx = new InitialDirContext( env );
+        SearchControls cons = new SearchControls();
+        cons.setSearchScope( SearchControls.OBJECT_SCOPE );
+        NamingEnumeration list = ctx.search( "", "(objectClass=*)", cons );
+        SearchResult result = null;
+        if ( list.hasMore() )
+        {
+            result = ( SearchResult ) list.next();
+        }
+        assertFalse( list.hasMore() );
+        list.close();
+
+        assertNotNull( result );
+        assertEquals( "", result.getName().trim() );
+    }
+
+    /**
+     * Test to make sure that if anonymous binds are allowed a user may search 
+     * within a a partition.
+     *
+     * @throws Exception if anything goes wrong
+     */
+    public void testAnonymousBindsEnabledBaseSearch() throws Exception
+    {
+        // Use the SUN JNDI provider to hit server port and bind as anonymous
+
+        final Hashtable env = new Hashtable();
+
+        env.put( Context.PROVIDER_URL, "ldap://localhost:" + port + "/" );
+        env.put( Context.SECURITY_AUTHENTICATION, "none" );
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+
+        InitialDirContext ctx = new InitialDirContext( env );
+        SearchControls cons = new SearchControls();
+        cons.setSearchScope( SearchControls.OBJECT_SCOPE );
+        NamingEnumeration list = ctx.search( "dc=apache,dc=org", "(objectClass=*)", cons );
+        SearchResult result = null;
+        if ( list.hasMore() )
+        {
+            result = ( SearchResult ) list.next();
+        }
+        assertFalse( list.hasMore() );
+        list.close();
+
+        assertNotNull( result );
+        assertNotNull( result.getAttributes().get("dc") );
+    }
+
+    /**
+     * Reproduces the problem with
+     * <a href="http://issues.apache.org/jira/browse/DIREVE-239">DIREVE-239</a>.
+     *
+     * @throws Exception if anything goes wrong
+     */
+    public void testAdminAccessBug() throws Exception
+    {
+        // Use the SUN JNDI provider to hit server port and bind as anonymous
+
+        final Hashtable env = new Hashtable();
+
+        env.put( Context.PROVIDER_URL, "ldap://localhost:" + port );
+        env.put("java.naming.ldap.version", "3");
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+
+        Attributes attributes = new BasicAttributes();
+        Attribute objectClass = new BasicAttribute( "objectClass" );
+        objectClass.add( "top" );
+        objectClass.add( "organizationalUnit" );
+        attributes.put( objectClass );
+        attributes.put( "ou", "blah" );
+        InitialDirContext ctx = new InitialDirContext( env );
+        ctx.createSubcontext( "ou=blah,ou=system", attributes );
+        SearchControls controls = new SearchControls();
+        controls.setSearchScope( SearchControls.OBJECT_SCOPE );
+        controls.setReturningAttributes( new String[] { "+" } );
+        NamingEnumeration list = ctx.search( "ou=blah,ou=system", "(objectClass=*)", controls );
+        SearchResult result = ( SearchResult ) list.next();
+        list.close();
+        Attribute creatorsName = result.getAttributes().get( "creatorsName" );
+        assertEquals( "", creatorsName.get() );
+    }
+
+
+    /**
+     * Test case for <a href="http://issues.apache.org/jira/browse/DIREVE-284" where users in
+     * mixed case partitions were not able to authenticate properly.  This test case creates
+     * a new partition under dc=aPache,dc=org, it then creates the example user in the JIRA
+     * issue and attempts to authenticate as that user.
+     *
+     * @throws Exception if the user cannot authenticate or test fails
+     */
+    public void testUserAuthOnMixedCaseSuffix() throws Exception
+    {
+        final Hashtable env = new Hashtable();
+
+        env.put( Context.PROVIDER_URL, "ldap://localhost:" + port + "/dc=aPache,dc=org" );
+        env.put("java.naming.ldap.version", "3");
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+        InitialDirContext ctx = new InitialDirContext( env );
+        Attributes attrs = ctx.getAttributes( "" );
+        assertTrue( attrs.get( "dc" ).get().equals( "aPache" ) );
+
+        Attributes user = new BasicAttributes( "cn", "Kate Bush", true );
+        Attribute oc = new BasicAttribute( "objectClass" );
+        oc.add( "top" );
+        oc.add( "person" );
+        oc.add( "organizationalPerson" );
+        oc.add( "inetOrgPerson" );
+        user.put( oc );
+        user.put( "sn", "Bush" );
+        user.put( "userPassword", "Aerial" );
+        ctx.createSubcontext( "cn=Kate Bush", user );
+
+        env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+        env.put( Context.SECURITY_CREDENTIALS, "Aerial" );
+        env.put( Context.SECURITY_PRINCIPAL, "cn=Kate Bush,dc=aPache,dc=org" );
+
+        InitialDirContext userCtx = new InitialDirContext( env );
+        assertNotNull( userCtx );
+    }
+
+
+    /**
+     * Tests to make sure undefined attributes in filter assertions are pruned and do not
+     * result in exceptions.
+     */
+    public void testBogusAttributeInSearchFilter() throws Exception
+    {
+        SearchControls cons = new SearchControls();
+        NamingEnumeration e = sysRoot.search( "", "(bogusAttribute=abc123)", cons );
+        assertNotNull( e );
+        assertEquals( e.getClass(), EmptyEnumeration.class );
+        e = sysRoot.search( "", "(!(bogusAttribute=abc123))", cons );
+        assertNotNull( e );
+        assertEquals( e.getClass(), EmptyEnumeration.class );
+        e = sysRoot.search( "", "(& (bogusAttribute=abc123)(bogusAttribute=abc123) )", cons );
+        assertNotNull( e );
+        assertEquals( e.getClass(), EmptyEnumeration.class );
+        e = sysRoot.search( "", "(& (bogusAttribute=abc123)(ou=abc123) )", cons );
+        assertNotNull( e );
+        assertFalse( e.getClass().equals( EmptyEnumeration.class ) );
+
+        e = sysRoot.search( "", "(OBJECTclass=*)", cons );
+        assertNotNull( e );
+        assertFalse( e.getClass().equals( EmptyEnumeration.class ) );
+
+        e = sysRoot.search( "", "(objectclass=*)", cons );
+        assertNotNull( e );
+        assertFalse( e.getClass().equals( EmptyEnumeration.class ) );
+    }
+    
+    
+    public void testFailureWithUnsupportedControl() throws Exception
+    {
+        Control unsupported = new Control()
+        {
+            boolean isCritical = true;
+            private static final long serialVersionUID = 1L;
+
+            public String getType()
+            {
+                return "1.1.1.1";
+            }
+
+            public void setType(String oid)
+            {
+            }
+
+            public byte[] getValue()
+            {
+                return new byte[0];
+            }
+
+            public void setValue(byte[] value)
+            {
+            }
+
+            public boolean isCritical()
+            {
+                return isCritical;
+            }
+
+            public void setCritical(boolean isCritical)
+            {
+                this.isCritical = isCritical;
+            }
+
+            public String getID()
+            {
+                return "1.1.1.1";
+            }
+
+            public byte[] getEncodedValue()
+            {
+                return new byte[0];
+            }
+        };
+        final Hashtable env = new Hashtable();
+
+        env.put( Context.PROVIDER_URL, "ldap://localhost:" + port + "/ou=system" );
+        env.put("java.naming.ldap.version", "3");
+        env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+        env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+        env.put( Context.SECURITY_CREDENTIALS, "secret" );
+        env.put( Context.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
+        InitialLdapContext ctx = new InitialLdapContext( env, null );
+
+        Attributes user = new BasicAttributes( "cn", "Kate Bush", true );
+        Attribute oc = new BasicAttribute( "objectClass" );
+        oc.add( "top" );
+        oc.add( "person" );
+        oc.add( "organizationalPerson" );
+        oc.add( "inetOrgPerson" );
+        user.put( oc );
+        user.put( "sn", "Bush" );
+        user.put( "userPassword", "Aerial" );
+        ctx.setRequestControls( new Control[] { unsupported } );
+        
+        try
+        {
+            ctx.createSubcontext( "cn=Kate Bush", user );
+        }
+        catch( OperationNotSupportedException e ) {}
+        
+        unsupported.setCritical( false );
+        DirContext kate = ctx.createSubcontext( "cn=Kate Bush", user );
+        assertNotNull( kate );
+        assertTrue( ArrayUtils.isEquals( Asn1StringUtils.getBytesUtf8( "Aerial" ), 
+            kate.getAttributes( "" ).get( "userPassword" ).get() ) );
+    }
+}

Added: directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyAddTest.java
URL: http://svn.apache.org/viewcvs/directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyAddTest.java?rev=375785&view=auto
==============================================================================
--- directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyAddTest.java (added)
+++ directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyAddTest.java Tue Feb  7 16:10:02 2006
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 2004 Solarsis Group LLC.
+ *
+ * Licensed under the Open Software License, Version 2.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://opensource.org/licenses/osl-2.1.php
+ *
+ * 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.server;
+
+import java.util.Hashtable;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.AttributeInUseException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InvalidAttributeIdentifierException;
+import javax.naming.directory.ModificationItem;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.directory.server.unit.AbstractServerTest;
+
+
+/**
+ * Testcase with different modify operations on a person entry. Each includes a
+ * single add op only. Created to demonstrate DIREVE-241 ("Adding an already
+ * existing attribute value with a modify operation does not cause an error.").
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class ModifyAddTest extends AbstractServerTest
+{
+    private LdapContext ctx = null;
+    public static final String RDN = "cn=Tori Amos";
+    public static final String PERSON_DESCRIPTION = "an American singer-songwriter";
+    
+    
+
+    /**
+     * Creation of required attributes of a person entry.
+     */
+    protected Attributes getPersonAttributes(String sn, String cn)
+    {
+        Attributes attributes = new BasicAttributes();
+        Attribute attribute = new BasicAttribute("objectClass");
+        attribute.add("top");
+        attribute.add("person");
+        attributes.put(attribute);
+        attributes.put("cn", cn);
+        attributes.put("sn", sn);
+
+        return attributes;
+    }
+
+    /**
+     * Create context and a person entry.
+     */
+    public void setUp() throws Exception
+    {
+        super.setUp();
+
+        Hashtable env = new Hashtable();
+        env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put("java.naming.provider.url", "ldap://localhost:" + port + "/ou=system");
+        env.put("java.naming.security.principal", "uid=admin,ou=system");
+        env.put("java.naming.security.credentials", "secret");
+        env.put("java.naming.security.authentication", "simple");
+
+        ctx = new InitialLdapContext(env, null);
+        assertNotNull(ctx);
+
+        // Create a person with description
+        Attributes attributes = this.getPersonAttributes("Amos", "Tori Amos");
+        attributes.put("description", "an American singer-songwriter");
+        ctx.createSubcontext(RDN, attributes);
+
+    }
+
+    /**
+     * Remove person entry and close context.
+     */
+    public void tearDown() throws Exception
+    {
+        ctx.unbind(RDN);
+        ctx.close();
+        ctx = null;
+        super.tearDown();
+    }
+
+    /**
+     * Add a new attribute to a person entry.
+     * 
+     * @throws NamingException
+     */
+    public void testAddNewAttributeValue() throws NamingException
+    {
+
+        // Add telephoneNumber attribute
+        String newValue = "1234567890";
+        Attributes attrs = new BasicAttributes("telephoneNumber", newValue);
+        ctx.modifyAttributes(RDN, DirContext.ADD_ATTRIBUTE, attrs);
+
+        // Verify, that attribute value is added
+        attrs = ctx.getAttributes(RDN);
+        Attribute attr = attrs.get("telephoneNumber");
+        assertNotNull(attr);
+        assertTrue(attr.contains(newValue));
+        assertEquals(1, attr.size());
+    }
+
+    /**
+     * Add a new attribute with two values.
+     * 
+     * @throws NamingException
+     */
+    public void testAddNewAttributeValues() throws NamingException
+    {
+
+        // Add telephoneNumber attribute
+        String[] newValues = { "1234567890", "999999999" };
+        Attribute attr = new BasicAttribute("telephoneNumber");
+        attr.add(newValues[0]);
+        attr.add(newValues[1]);
+        Attributes attrs = new BasicAttributes();
+        attrs.put(attr);
+        ctx.modifyAttributes(RDN, DirContext.ADD_ATTRIBUTE, attrs);
+
+        // Verify, that attribute values are present
+        attrs = ctx.getAttributes(RDN);
+        attr = attrs.get("telephoneNumber");
+        assertNotNull(attr);
+        assertTrue(attr.contains(newValues[0]));
+        assertTrue(attr.contains(newValues[1]));
+        assertEquals(newValues.length, attr.size());
+    }
+
+    /**
+     * Add an additional value.
+     * 
+     * @throws NamingException
+     */
+    public void testAddAdditionalAttributeValue() throws NamingException
+    {
+
+        // A new description attribute value
+        String newValue = "A new description for this person";
+        assertFalse(newValue.equals(PERSON_DESCRIPTION));
+        Attributes attrs = new BasicAttributes("description", newValue);
+
+        ctx.modifyAttributes(RDN, DirContext.ADD_ATTRIBUTE, attrs);
+
+        // Verify, that attribute value is added
+        attrs = ctx.getAttributes(RDN);
+        Attribute attr = attrs.get("description");
+        assertNotNull(attr);
+        assertTrue(attr.contains(newValue));
+        assertTrue(attr.contains(PERSON_DESCRIPTION));
+        assertEquals(2, attr.size());
+    }
+
+    /**
+     * Try to add an already existing attribute value.
+     * 
+     * Expected behaviour: Modify operation fails with an
+     * AttributeInUseException. Original LDAP Error code: 20 (Indicates that the
+     * attribute value specified in a modify or add operation already exists as
+     * a value for that attribute).
+     * 
+     * @throws NamingException
+     */
+    public void testAddExistingAttributeValue() throws NamingException
+    {
+
+        // Change description attribute
+        Attributes attrs = new BasicAttributes("description", PERSON_DESCRIPTION);
+        try
+        {
+            ctx.modifyAttributes(RDN, DirContext.ADD_ATTRIBUTE, attrs);
+            fail("Adding an already existing atribute value should fail.");
+        }
+        catch (AttributeInUseException e)
+        {
+            // expected behaviour
+        }
+
+        // Verify, that attribute is still there, and is the only one
+        attrs = ctx.getAttributes(RDN);
+        Attribute attr = attrs.get("description");
+        assertNotNull(attr);
+        assertTrue(attr.contains(PERSON_DESCRIPTION));
+        assertEquals(1, attr.size());
+    }
+
+    /**
+     * Try to add a duplicate attribute value to an entry, where this attribute
+     * is already present (objectclass in this case). Expected behaviour is that
+     * the modify operation causes an error (error code 20, "Attribute or value
+     * exists").
+     * 
+     * @throws NamingException
+     */
+    public void testAddDuplicateValueToExistingAttribute() throws NamingException
+    {
+        // modify object classes, add a new value twice
+        Attribute ocls = new BasicAttribute("objectClass", "organizationalPerson");
+        ModificationItem[] modItems = new ModificationItem[2];
+        modItems[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, ocls);
+        modItems[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, ocls);
+        try {
+            ctx.modifyAttributes(RDN, modItems);
+            fail("Adding a duplicate attribute value should cause an error.");
+        } catch (AttributeInUseException ex) {}
+
+        // Check, whether attribute objectClass is unchanged
+        Attributes attrs = ctx.getAttributes(RDN);
+        ocls = attrs.get("objectClass");
+        assertEquals(ocls.size(), 2);
+        assertTrue(ocls.contains("top"));
+        assertTrue(ocls.contains("person"));
+    }
+
+    /**
+     * Try to add a duplicate attribute value to an entry, where this attribute
+     * is not present. Expected behaviour is that the modify operation causes an
+     * error (error code 20, "Attribute or value exists").
+     * 
+     * @throws NamingException
+     */
+    public void testAddDuplicateValueToNewAttribute() throws NamingException
+    {
+        // add the same description value twice
+        Attribute desc = new BasicAttribute("description", "another description value besides songwriter");
+        ModificationItem[] modItems = new ModificationItem[2];
+        modItems[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, desc);
+        modItems[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE, desc);
+        try {
+            ctx.modifyAttributes(RDN, modItems);
+            fail("Adding a duplicate attribute value should cause an error.");
+        } catch (AttributeInUseException ex) {}
+
+        // Check, whether attribute description is still not present
+        Attributes attrs = ctx.getAttributes(RDN);
+        assertEquals( 1, attrs.get("description").size() );
+    }
+    
+    /**
+     * Create an entry with a bad attribute : this should fail.
+     * 
+     * @throws NamingException
+     */
+    public void testAddUnexistingAttribute() throws NamingException
+    {
+        Hashtable env = new Hashtable();
+        env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put("java.naming.provider.url", "ldap://localhost:" + port + "/ou=system");
+        env.put("java.naming.security.principal", "uid=admin,ou=system");
+        env.put("java.naming.security.credentials", "secret");
+        env.put("java.naming.security.authentication", "simple");
+
+        ctx = new InitialLdapContext(env, null);
+        assertNotNull(ctx);
+    
+        // Create a third person with a voice attribute
+        Attributes attributes = this.getPersonAttributes("Jackson", "Michael Jackson");
+        attributes.put("voice", "He is bad ...");
+        
+        try
+        {
+        ctx.createSubcontext( "cn=Mickael Jackson", attributes);
+        }
+        catch ( InvalidAttributeIdentifierException iaie)
+        {
+            assertTrue( true );
+            return;
+        }
+        
+        fail( "Should never reach this point" );
+    }
+
+    /**
+     * Modu=ify the entry with a bad attribute : this should fail 
+     * 
+     * @throws NamingException
+     */
+    public void testSearchBadAttribute() throws NamingException
+    {
+        // Add a not existing attribute
+        String newValue = "unbelievable";
+        Attributes attrs = new BasicAttributes( "voice", newValue );
+        
+        try
+        {
+            ctx.modifyAttributes( RDN, DirContext.ADD_ATTRIBUTE, attrs );
+        }
+        catch (InvalidAttributeIdentifierException iaie )
+        {
+            // We have a failure : the attribute is unknown in the schema
+            assertTrue( true );
+            return;
+        }
+        
+        fail( "Cannot reach this point" );
+    }
+}

Added: directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRdnTest.java
URL: http://svn.apache.org/viewcvs/directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRdnTest.java?rev=375785&view=auto
==============================================================================
--- directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRdnTest.java (added)
+++ directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRdnTest.java Tue Feb  7 16:10:02 2006
@@ -0,0 +1,277 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed 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.server;
+
+import java.util.Hashtable;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.directory.server.unit.AbstractServerTest;
+
+
+/**
+ * Testcase with different modify DN operations on a person entry.
+ * Originally created to demonstrate DIREVE-173.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class ModifyRdnTest extends AbstractServerTest
+{
+
+    private LdapContext ctx = null;
+
+    /**
+     * Create attributes for a person entry.
+     */
+    protected Attributes getPersonAttributes(String sn, String cn)
+    {
+        Attributes attributes = new BasicAttributes();
+        Attribute attribute = new BasicAttribute("objectClass");
+        attribute.add("top");
+        attribute.add("person");
+        attributes.put(attribute);
+        attributes.put("cn", cn);
+        attributes.put("sn", sn);
+        attributes.put("description", cn + " is a person.");
+
+        return attributes;
+    }
+
+    /**
+     * Create context
+     */
+    public void setUp() throws Exception
+    {
+        super.setUp();
+
+        Hashtable env = new Hashtable();
+        env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put("java.naming.provider.url", "ldap://localhost:" + port + "/ou=system" ); 
+        env.put("java.naming.security.principal", "uid=admin,ou=system" ); 
+        env.put("java.naming.security.credentials", "secret" );
+        env.put("java.naming.security.authentication", "simple");
+        ctx = new InitialLdapContext(env, null);
+        assertNotNull(ctx);
+    }
+
+    /**
+     * Close context
+     */
+    public void tearDown() throws Exception
+    {
+        ctx.close();
+        ctx = null;
+
+        super.tearDown();
+    }
+
+    /**
+     * Just a little test to check wether opening the connection succeeds.
+     */
+    public void testSetUpTearDown() throws NamingException
+    {
+        assertNotNull(ctx);
+    }
+
+    /**
+     * Modify Rdn of an entry, delete its old rdn value.
+     * 
+     * @throws NamingException
+     */
+    public void testModifyRdnAndDeleteOld() throws NamingException
+    {
+        // Create a person, cn value is rdn
+        String oldCn = "Myra Ellen Amos";
+        String oldRdn = "cn=" + oldCn;
+        Attributes attributes = this.getPersonAttributes("Amos", oldCn);
+        ctx.createSubcontext(oldRdn, attributes);
+
+        // modify Rdn
+        String newCn = "Tori Amos";
+        String newRdn = "cn=" + newCn;
+        ctx.addToEnvironment("java.naming.ldap.deleteRDN", "true");
+        ctx.rename(oldRdn, newRdn);
+
+        // Check, whether old Entry does not exists
+        try {
+            ctx.lookup(oldRdn);
+            fail("Entry must not exist");
+        } catch (NameNotFoundException ignored) {
+            // expected behaviour
+            assertTrue(true);
+        }
+
+        // Check, whether new Entry exists
+        DirContext tori = (DirContext) ctx.lookup(newRdn);
+        assertNotNull(tori);
+
+        // Check values of cn
+        Attribute cn = tori.getAttributes("").get("cn");
+        assertTrue(cn.contains(newCn));
+        assertTrue(!cn.contains(oldCn)); // old value is gone
+        assertEquals(1, cn.size());
+
+        // Remove entry (use new rdn)
+        ctx.unbind(newRdn);
+    }
+
+    /**
+     * Modify Rdn of an entry, keep its old rdn value.
+     * 
+     * @throws NamingException
+     */
+    public void testModifyRdnAndKeepOld() throws NamingException
+    {
+        // Create a person, cn value is rdn
+        String oldCn = "Myra Ellen Amos";
+        String oldRdn = "cn=" + oldCn;
+        Attributes attributes = this.getPersonAttributes("Amos", oldCn);
+        ctx.createSubcontext(oldRdn, attributes);
+
+        // modify Rdn
+        String newCn = "Tori Amos";
+        String newRdn = "cn=" + newCn;
+        ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false");
+        ctx.rename(oldRdn, newRdn);
+
+        // Check, whether old entry does not exist
+        try {
+            ctx.lookup(oldRdn);
+            fail("Entry must not exist");
+        } catch (NameNotFoundException ignored) {
+            // expected behaviour
+            assertTrue(true);
+        }
+
+        // Check, whether new entry exists
+        DirContext tori = (DirContext) ctx.lookup(newRdn);
+        assertNotNull(tori);
+
+        // Check values of cn
+        Attribute cn = tori.getAttributes("").get("cn");
+        assertTrue(cn.contains(newCn));
+        assertTrue(cn.contains(oldCn)); // old value is still there
+        assertEquals(2, cn.size());
+
+        // Remove entry (use new rdn)
+        ctx.unbind(newRdn);
+    }
+
+    /**
+     * Modify Rdn of an entry, delete its old rdn value. Here, the rdn attribute
+     * cn has another value as well.
+     * 
+     * @throws NamingException
+     */
+    public void testModifyRdnAndDeleteOldVariant() throws NamingException
+    {
+        // Create a person, cn value is rdn
+        String oldCn = "Myra Ellen Amos";
+        String oldRdn = "cn=" + oldCn;
+        Attributes attributes = this.getPersonAttributes("Amos", oldCn);
+
+        // add a second cn value
+        String alternateCn = "Myra E. Amos";
+        Attribute cn = attributes.get("cn");
+        cn.add(alternateCn);
+        assertEquals(2, cn.size());
+
+        ctx.createSubcontext(oldRdn, attributes);
+
+        // modify Rdn
+        String newCn = "Tori Amos";
+        String newRdn = "cn=" + newCn;
+        ctx.addToEnvironment("java.naming.ldap.deleteRDN", "true");
+        ctx.rename(oldRdn, newRdn);
+
+        // Check, whether old Entry does not exist anymore
+        try {
+            ctx.lookup(oldRdn);
+            fail("Entry must not exist");
+        } catch (NameNotFoundException ignored) {
+            // expected behaviour
+            assertTrue(true);
+        }
+
+        // Check, whether new Entry exists
+        DirContext tori = (DirContext) ctx.lookup(newRdn);
+        assertNotNull(tori);
+
+        // Check values of cn
+        cn = tori.getAttributes("").get("cn");
+        assertTrue(cn.contains(newCn));
+        assertTrue(!cn.contains(oldCn)); // old value is gone
+        assertTrue(cn.contains(alternateCn)); // alternate value is still available
+        assertEquals(2, cn.size());
+
+        // Remove entry (use new rdn)
+        ctx.unbind(newRdn);
+    }
+    
+    /**
+     * Modify DN of an entry, changing RDN from cn to sn.
+     * 
+     * @throws NamingException
+     */
+    public void testModifyRdnDifferentAttribute() throws NamingException {
+
+        // Create a person, cn value is rdn
+        String cnVal = "Tori Amos";
+        String snVal = "Amos";
+        String oldRdn = "cn=" + cnVal;
+        Attributes attributes = this.getPersonAttributes(snVal, cnVal);
+        ctx.createSubcontext(oldRdn, attributes);
+
+        // modify Rdn from cn=... to sn=...
+        String newRdn = "sn=" + snVal;
+        ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false");
+        ctx.rename(oldRdn, newRdn);
+
+        // Check, whether old Entry does not exists
+        try {
+            ctx.lookup(oldRdn);
+            fail("Entry must not exist");
+        } catch (NameNotFoundException ignored) {
+            // expected behaviour
+        }
+
+        // Check, whether new Entry exists
+        DirContext tori = (DirContext) ctx.lookup(newRdn);
+        assertNotNull(tori);
+
+        // Check values of cn and sn
+        // especially the number of cn and sn occurences
+        Attribute cn = tori.getAttributes("").get("cn");
+        assertTrue(cn.contains(cnVal));
+        assertEquals("Number of cn occurences", 1, cn.size());
+        Attribute sn = tori.getAttributes("").get("sn");
+        assertTrue(sn.contains(snVal));
+        assertEquals("Number of sn occurences", 1, sn.size());
+        
+        // Remove entry (use new rdn)
+        ctx.unbind(newRdn);
+    }
+}

Added: directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRemoveTest.java
URL: http://svn.apache.org/viewcvs/directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRemoveTest.java?rev=375785&view=auto
==============================================================================
--- directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRemoveTest.java (added)
+++ directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/ModifyRemoveTest.java Tue Feb  7 16:10:02 2006
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2004 Solarsis Group LLC.
+ *
+ * Licensed under the Open Software License, Version 2.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://opensource.org/licenses/osl-2.1.php
+ *
+ * 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.server;
+
+import java.util.Hashtable;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InvalidAttributeIdentifierException;
+import javax.naming.directory.NoSuchAttributeException;
+import javax.naming.directory.SchemaViolationException;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.directory.server.unit.AbstractServerTest;
+
+
+/**
+ * Testcase with different modify operations on a person entry. Each includes a
+ * single removal op only.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class ModifyRemoveTest extends AbstractServerTest
+{
+
+    private LdapContext ctx = null;
+
+    public static final String RDN = "cn=Tori Amos";
+
+    /**
+     * Creation of required attributes of a person entry.
+     */
+    protected Attributes getPersonAttributes(String sn, String cn)
+    {
+        Attributes attributes = new BasicAttributes();
+        Attribute attribute = new BasicAttribute("objectClass");
+        attribute.add("top");
+        attribute.add("person");
+        attributes.put(attribute);
+        attributes.put("cn", cn);
+        attributes.put("sn", sn);
+
+        return attributes;
+    }
+
+    /**
+     * Create context and a person entry.
+     */
+    public void setUp() throws Exception
+    {
+        super.setUp();
+
+        Hashtable env = new Hashtable();
+        env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
+        env.put("java.naming.provider.url", "ldap://localhost:" + port + "/ou=system");
+        env.put("java.naming.security.principal", "uid=admin,ou=system");
+        env.put("java.naming.security.credentials", "secret");
+        env.put("java.naming.security.authentication", "simple");
+
+        ctx = new InitialLdapContext(env, null);
+        assertNotNull(ctx);
+
+        // Create a person with description
+        Attributes attributes = this.getPersonAttributes("Amos", "Tori Amos");
+        attributes.put("description", "an American singer-songwriter");
+        ctx.createSubcontext(RDN, attributes);
+
+    }
+
+    /**
+     * Remove person entry and close context.
+     */
+    public void tearDown() throws Exception
+    {
+        ctx.unbind(RDN);
+        ctx.close();
+        ctx = null;
+        super.tearDown();
+    }
+
+    /**
+     * Just a little test to check wether opening the connection and creation of
+     * the person succeeds succeeds.
+     */
+    public void testSetUpTearDown() throws NamingException
+    {
+        assertNotNull(ctx);
+        DirContext tori = (DirContext) ctx.lookup(RDN);
+        assertNotNull(tori);
+    }
+
+    /**
+     * Remove an attribute, which is not required.
+     * 
+     * Expected result: After successful deletion, attribute is not present in
+     * entry.
+     * 
+     * @throws NamingException
+     */
+    public void testRemoveNotRequiredAttribute() throws NamingException
+    {
+        // Remove description Attribute
+        Attribute attr = new BasicAttribute("description");
+        Attributes attrs = new BasicAttributes();
+        attrs.put(attr);
+        ctx.modifyAttributes(RDN, DirContext.REMOVE_ATTRIBUTE, attrs);
+
+        // Verify, that attribute is deleted
+        attrs = ctx.getAttributes(RDN);
+        attr = attrs.get("description");
+        assertNull(attr);
+    }
+
+    /**
+     * Remove two not required attributes.
+     * 
+     * Expected result: After successful deletion, both attributes ar not
+     * present in entry.
+     * 
+     * @throws NamingException
+     */
+    public void testRemoveTwoNotRequiredAttributes() throws NamingException
+    {
+
+        // add telephoneNumber to entry
+        Attributes tn = new BasicAttributes("telephoneNumber", "12345678");
+        ctx.modifyAttributes(RDN, DirContext.ADD_ATTRIBUTE, tn);
+
+        // Remove description and telephoneNumber to Attribute
+        Attributes attrs = new BasicAttributes();
+        attrs.put(new BasicAttribute("description"));
+        attrs.put(new BasicAttribute("telephoneNumber"));
+        ctx.modifyAttributes(RDN, DirContext.REMOVE_ATTRIBUTE, attrs);
+
+        // Verify, that attributes are deleted
+        attrs = ctx.getAttributes(RDN);
+        assertNull(attrs.get("description"));
+        assertNull(attrs.get("telephoneNumber"));
+        assertNotNull(attrs.get("cn"));
+        assertNotNull(attrs.get("sn"));
+    }
+
+    /**
+     * Remove a required attribute. The sn attribute of the person entry is used
+     * here.
+     * 
+     * Expected Result: Deletion fails with NamingException (Schema Violation).
+     * 
+     * @throws NamingException
+     */
+    public void testRemoveRequiredAttribute() throws NamingException
+    {
+
+        // Remove sn attribute
+        Attribute attr = new BasicAttribute("sn");
+        Attributes attrs = new BasicAttributes();
+        attrs.put(attr);
+
+        try {
+            ctx.modifyAttributes(RDN, DirContext.REMOVE_ATTRIBUTE, attrs);
+            fail("Deletion of required attribute should fail.");
+        } catch (SchemaViolationException e) {
+            // expected behaviour
+        }
+    }
+
+    /**
+     * Remove a required attribute from RDN.
+     * 
+     * Expected Result: Deletion fails with SchemaViolationException.
+     * 
+     * @throws NamingException
+     */
+    public void testRemovePartOfRdn() throws NamingException
+    {
+
+        // Remove sn attribute
+        Attribute attr = new BasicAttribute("cn");
+        Attributes attrs = new BasicAttributes();
+        attrs.put(attr);
+
+        try {
+            ctx.modifyAttributes(RDN, DirContext.REMOVE_ATTRIBUTE, attrs);
+            fail("Deletion of RDN attribute should fail.");
+        } catch (SchemaViolationException e) {
+            // expected behaviour
+        }
+    }
+
+    /**
+     * Remove a not required attribute from RDN.
+     * 
+     * Expected Result: Deletion fails with SchemaViolationException.
+     * 
+     * @throws NamingException
+     */
+    public void testRemovePartOfRdnNotRequired() throws NamingException
+    {
+
+        // Change RDN to another attribute
+        String newRdn = "description=an American singer-songwriter";
+        ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false");
+        ctx.rename(RDN, newRdn);
+
+        // Remove description, which is now RDN attribute
+        Attribute attr = new BasicAttribute("description");
+        Attributes attrs = new BasicAttributes();
+        attrs.put(attr);
+
+        try {
+            ctx.modifyAttributes(newRdn, DirContext.REMOVE_ATTRIBUTE, attrs);
+            fail("Deletion of RDN attribute should fail.");
+        } catch (SchemaViolationException e) {
+            // expected behaviour
+        }
+
+        // Change RDN back to original
+        ctx.addToEnvironment("java.naming.ldap.deleteRDN", "false");
+        ctx.rename(newRdn, RDN);
+    }
+
+    /**
+     * Remove a an attribute which is not present on the entry, but in the
+     * schema.
+     * 
+     * Expected result: Deletion fails with NoSuchAttributeException
+     * 
+     * @throws NamingException
+     */
+    public void testRemoveAttributeNotPresent() throws NamingException
+    {
+
+        // Remove telephoneNumber Attribute
+        Attribute attr = new BasicAttribute("telephoneNumber");
+        Attributes attrs = new BasicAttributes();
+        attrs.put(attr);
+
+        try {
+            ctx.modifyAttributes(RDN, DirContext.REMOVE_ATTRIBUTE, attrs);
+            fail("Deletion of attribute, which is not present in the entry, should fail.");
+        } catch (NoSuchAttributeException e) {
+            // expected behaviour
+        }
+    }
+
+    /**
+     * Remove a an attribute which is not present in the schema.
+     * 
+     * Expected result: Deletion fails with NoSuchAttributeException
+     * 
+     * @throws NamingException
+     */
+    public void testRemoveAttributeNotValid() throws NamingException
+    {
+
+        // Remove phantasy attribute
+        Attribute attr = new BasicAttribute("XXX");
+        Attributes attrs = new BasicAttributes();
+        attrs.put(attr);
+
+        try {
+            ctx.modifyAttributes(RDN, DirContext.REMOVE_ATTRIBUTE, attrs);
+            fail("Deletion of an invalid attribute should fail.");
+        } catch (NoSuchAttributeException e) {
+            // expected behaviour
+        } catch (InvalidAttributeIdentifierException e) {
+            // expected behaviour
+        }
+    }
+
+}

Added: directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/PersistentSearchTest.java
URL: http://svn.apache.org/viewcvs/directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/PersistentSearchTest.java?rev=375785&view=auto
==============================================================================
--- directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/PersistentSearchTest.java (added)
+++ directory/trunks/apacheds/server-unit/src/test/java/org/apache/directory/server/PersistentSearchTest.java Tue Feb  7 16:10:02 2006
@@ -0,0 +1,744 @@
+/*
+ * Copyright (c) 2004 Solarsis Group LLC.
+ *
+ * Licensed under the Open Software License, Version 2.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://opensource.org/licenses/osl-2.1.php
+ *
+ * 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.server;
+
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.BasicAttributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchResult;
+import javax.naming.event.EventContext;
+import javax.naming.event.EventDirContext;
+import javax.naming.event.NamespaceChangeListener;
+import javax.naming.event.NamingEvent;
+import javax.naming.event.NamingExceptionEvent;
+import javax.naming.event.ObjectChangeListener;
+import javax.naming.ldap.Control;
+import javax.naming.ldap.HasControls;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.directory.server.unit.AbstractServerTest;
+import org.apache.directory.shared.ldap.codec.search.controls.ChangeType;
+import org.apache.directory.shared.ldap.codec.search.controls.EntryChangeControl;
+import org.apache.directory.shared.ldap.codec.search.controls.EntryChangeControlDecoder;
+import org.apache.directory.shared.ldap.message.PersistentSearchControl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Testcase which tests the correct operation of the persistent search control.
+ * 
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev$
+ */
+public class PersistentSearchTest extends AbstractServerTest
+{
+    public static final Logger log = LoggerFactory.getLogger( PersistentSearchTest.class );
+    public static final String PERSON_DESCRIPTION = "an American singer-songwriter";
+    public static final String RDN = "cn=Tori Amos";
+    private LdapContext ctx = null;
+
+
+
+    /**
+     * Creation of required attributes of a person entry.
+     */
+    protected Attributes getPersonAttributes( String sn, String cn )
+    {
+        Attributes attributes = new BasicAttributes();
+        Attribute attribute = new BasicAttribute( "objectClass" );
+        attribute.add( "top" );
+        attribute.add( "person" );
+        attributes.put( attribute );
+        attributes.put( "cn", cn );
+        attributes.put( "sn", sn );
+
+        return attributes;
+    }
+
+
+    /**
+     * Create context and a person entry.
+     */
+    public void setUp() throws Exception
+    {
+        super.setUp();
+
+        Hashtable env = new Hashtable();
+        env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+        env.put( "java.naming.provider.url", "ldap://localhost:" + port + "/ou=system" );
+        env.put( "java.naming.security.principal", "uid=admin,ou=system" );
+        env.put( "java.naming.security.credentials", "secret" );
+        env.put( "java.naming.security.authentication", "simple" );
+
+        ctx = new InitialLdapContext( env, null );
+        assertNotNull( ctx );
+
+        // Create a person with description
+        Attributes attributes = this.getPersonAttributes( "Amos", "Tori Amos" );
+        attributes.put( "description", PERSON_DESCRIPTION );
+        ctx.createSubcontext( RDN, attributes );
+    }
+
+
+    /**
+     * Remove person entry and close context.
+     */
+    public void tearDown() throws Exception
+    {
+        try
+        {
+            ctx.unbind( RDN );
+            ctx.close();
+            ctx = null;
+            super.tearDown();
+        }
+        catch( Throwable t )
+        {
+        }
+    }
+
+
+    /**
+     * Shows correct notifications for modify(4) changes.
+     */
+    public void testPsearchModify() throws Exception
+    {
+        PSearchListener listener = new PSearchListener();
+        Thread t = new Thread( listener, "PSearchListener" );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, 
+            new BasicAttributes( "description", PERSON_DESCRIPTION, true ) );
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 200 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+    }
+
+
+    /**
+     * Shows correct notifications for moddn(8) changes.
+     */
+    public void testPsearchModifyDn() throws Exception
+    {
+        PSearchListener listener = new PSearchListener();
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.rename( RDN, "cn=Jack Black" );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+    }
+
+    
+    /**
+     * Shows correct notifications for delete(2) changes.
+     */
+    public void testPsearchDelete() throws Exception
+    {
+        PSearchListener listener = new PSearchListener();
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.destroySubcontext( RDN );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+    }
+
+    
+    /**
+     * Shows correct notifications for add(1) changes.
+     */
+    public void testPsearchAdd() throws Exception
+    {
+        PSearchListener listener = new PSearchListener();
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+    }
+
+
+    /**
+     * Shows correct notifications for modify(4) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchModifyWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener, "PSearchListener" );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, 
+            new BasicAttributes( "description", PERSON_DESCRIPTION, true ) );
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 200 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.MODIFY );
+    }
+
+
+    /**
+     * Shows correct notifications for moddn(8) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchModifyDnWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.rename( RDN, "cn=Jack Black" );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.MODDN );
+        assertEquals( ( RDN + ",ou=system" ).toLowerCase(), listener.result.control.getPreviousDn().toLowerCase() );
+    }
+
+    
+    /**
+     * Shows correct notifications for delete(2) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchDeleteWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.destroySubcontext( RDN );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.DELETE );
+    }
+
+    
+    /**
+     * Shows correct notifications for add(1) changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchAddWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.ADD );
+    }
+
+    
+    /**
+     * Shows correct notifications for only add(1) and modify(4) registered changes with returned 
+     * EntryChangeControl.
+     */
+    public void testPsearchAddModifyEnabledWithEC() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        control.setChangeTypes( ChangeType.ADD_VALUE );
+        control.enableNotification( ChangeType.MODIFY );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.ADD );
+        listener.result = null;
+        t = new Thread( listener );
+        t.start();
+        
+        ctx.destroySubcontext( "cn=Jack Black" );
+        
+        start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+
+        assertNull( listener.result );
+
+        // thread is still waiting for notifications try a modify
+        ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, 
+            new BasicAttributes( "description", PERSON_DESCRIPTION, true ) );
+        start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 200 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.MODIFY );
+    }
+    
+
+    /**
+     * Shows correct notifications for add(1) changes with returned 
+     * EntryChangeControl and changesOnly set to false so we return
+     * the first set of entries.
+     * 
+     * This test is commented out because it exhibits some producer
+     * consumer lockups (server and client being in same process)
+     * 
+     * PLUS ALL THIS GARBAGE IS TIME DEPENDENT!!!!!
+     */
+//    public void testPsearchAddWithECAndFalseChangesOnly() throws Exception
+//    {
+//        PersistentSearchControl control = new PersistentSearchControl();
+//        control.setReturnECs( true );
+//        control.setChangesOnly( false );
+//        PSearchListener listener = new PSearchListener( control );
+//        Thread t = new Thread( listener );
+//        t.start();
+//        
+//        Thread.sleep( 3000 );
+//
+//        assertEquals( 5, listener.count );
+//        ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
+//        
+//        long start = System.currentTimeMillis();
+//        while ( t.isAlive() )
+//        {
+//            Thread.sleep( 100 );
+//            if ( System.currentTimeMillis() - start > 3000 )
+//            {
+//                break;
+//            }
+//        }
+//        
+//        assertEquals( 6, listener.count );
+//        assertNotNull( listener.result );
+//        // darn it getting normalized name back
+//        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+//        assertEquals( listener.result.control.getChangeType(), ChangeType.ADD );
+//    }
+
+
+    /**
+     * Shows notifications functioning with the JNDI notification API of the SUN
+     * provider.
+     */
+    public void testPsearchUsingJndiNotifications() throws Exception
+    {
+        Hashtable env = new Hashtable();
+        env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+        env.put( "java.naming.provider.url", "ldap://localhost:" + port + "/ou=system" );
+        env.put( "java.naming.security.principal", "uid=admin,ou=system" );
+        env.put( "java.naming.security.credentials", "secret" );
+        env.put( "java.naming.security.authentication", "simple" );
+
+        JndiNotificationListener listener = new JndiNotificationListener();
+        InitialDirContext idc = new InitialDirContext( env );
+        EventDirContext edc = ( EventDirContext ) ( idc.lookup( "" ) );
+        edc.addNamingListener( "", EventContext.ONELEVEL_SCOPE, listener );
+        
+        while ( listener.list.isEmpty() )
+        {
+            Thread.sleep( 250 );
+            String rdn = "cn=Jack Black";
+            ctx.createSubcontext( rdn, getPersonAttributes( "Black", "Jack Black" ) );
+            ctx.destroySubcontext( rdn );
+        }
+        
+        NamingEvent event = ( NamingEvent ) listener.list.get( 0 );
+        assertEquals( edc, event.getSource() );
+    }
+
+    
+    /**
+     * Shows notifications functioning with the JNDI notification API of the SUN
+     * provider.
+     */
+    public void testPsearchAbandon() throws Exception
+    {
+        PersistentSearchControl control = new PersistentSearchControl();
+        control.setReturnECs( true );
+        PSearchListener listener = new PSearchListener( control );
+        Thread t = new Thread( listener );
+        t.start();
+        
+        while( ! listener.isReady )
+        {
+            Thread.sleep( 100 );
+        }
+        Thread.sleep( 250 );
+
+        ctx.createSubcontext( "cn=Jack Black", getPersonAttributes( "Black", "Jack Black" ) );
+        
+        long start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( "cn=Jack Black".toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.ADD );
+        listener.result = null;
+        t = new Thread( listener );
+        t.start();
+        
+        ctx.destroySubcontext( "cn=Jack Black" );
+        
+        start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 100 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+
+        // there seems to be a race condition here
+        // assertNull( listener.result );
+
+        // thread is still waiting for notifications try a modify
+        ctx.modifyAttributes( RDN, DirContext.REMOVE_ATTRIBUTE, 
+            new BasicAttributes( "description", PERSON_DESCRIPTION, true ) );
+        start = System.currentTimeMillis();
+        while ( t.isAlive() )
+        {
+            Thread.sleep( 200 );
+            if ( System.currentTimeMillis() - start > 3000 )
+            {
+                break;
+            }
+        }
+        
+        assertNotNull( listener.result );
+        // darn it getting normalized name back
+        assertEquals( RDN.toLowerCase(), listener.result.getName().toLowerCase() );
+        assertEquals( listener.result.control.getChangeType(), ChangeType.MODIFY );
+    }
+
+    
+    class JndiNotificationListener implements NamespaceChangeListener, ObjectChangeListener
+    {
+        ArrayList list = new ArrayList();
+        
+        public void objectAdded(NamingEvent evt)
+        {
+            list.add( 0, evt );
+        }
+
+        public void objectRemoved(NamingEvent evt)
+        {
+            list.add( 0, evt );
+        }
+
+        public void objectRenamed(NamingEvent evt)
+        {
+            list.add( 0, evt );
+        }
+
+        public void namingExceptionThrown(NamingExceptionEvent evt)
+        {
+            list.add( 0, evt );
+        }
+
+        public void objectChanged(NamingEvent evt)
+        {
+            list.add( 0, evt );
+        }
+    }
+    
+    
+    class PSearchListener implements Runnable
+    {
+        boolean isReady = false;
+        PSearchNotification result;
+        int count = 0;
+        final PersistentSearchControl control;
+
+        PSearchListener() { control = new PersistentSearchControl(); }
+        PSearchListener( PersistentSearchControl control ) { this.control = control; }
+        
+        public void run()
+        {
+            NamingEnumeration list = null;
+            control.setCritical( true );
+            Control[] ctxCtls = new Control[] { control };
+            
+            try
+            {
+                ctx.setRequestControls( ctxCtls );
+                isReady = true;
+                list = ctx.search( "", "objectClass=*", null );
+                EntryChangeControl ecControl = null;
+                
+                while( list.hasMore() )
+                {
+                    Control[] controls = null;
+                    SearchResult sresult = ( SearchResult ) list.next();
+                    count++;
+                    if ( sresult instanceof HasControls )
+                    {
+                        controls = ( ( HasControls ) sresult ).getControls();
+                        if ( controls != null )
+                        {
+                            for ( int ii = 0; ii < controls.length; ii ++ )
+                            {
+                                if ( controls[ii].getID().equals( 
+                                    org.apache.directory.shared.ldap.message.EntryChangeControl.CONTROL_OID ) )
+                                {
+                                    EntryChangeControlDecoder decoder = new EntryChangeControlDecoder();
+                                    ecControl = ( EntryChangeControl ) decoder.decode( controls[ii].getEncodedValue() );
+                                }
+                            }
+                        }
+                    }
+                    result = new PSearchNotification( sresult, ecControl );
+                    break;
+                }
+            }
+            catch( Exception e ) 
+            {
+                e.printStackTrace();
+            }
+            finally
+            {
+                if ( list != null )
+                {
+                    try { list.close(); } catch ( Exception e ) { e.printStackTrace(); };
+                }
+            }
+        }
+    }
+
+
+    class PSearchNotification extends SearchResult
+    {
+        private static final long serialVersionUID = 1L;
+        final EntryChangeControl control;
+        
+        public PSearchNotification( SearchResult result, EntryChangeControl control )
+        {
+            super( result.getName(), result.getClassName(), result.getObject(), result.getAttributes(), result.isRelative() );
+            this.control = control;
+        }
+        
+        public String toString()
+        {
+            StringBuffer buf = new StringBuffer();
+            buf.append( "DN: " ).append( getName() ).append( "\n" );
+            if ( control != null )
+            {
+                buf.append( "    EntryChangeControl =\n" );
+                buf.append( "       changeType   : " ).append( control.getChangeType() ).append( "\n" );
+                buf.append( "       previousDN   : " ).append( control.getPreviousDn() ).append( "\n" );
+                buf.append( "       changeNumber : " ).append( control.getChangeNumber() ).append( "\n" );
+            }
+            return buf.toString();
+        }
+    }
+}



Mime
View raw message