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 2007/09/25 19:15:57 UTC
svn commit: r579319 - in /directory/apacheds/trunk/server-unit/src:
main/java/org/apache/directory/server/unit/AbstractServerFastTest.java
test/java/org/apache/directory/server/FastDelITest.java
test/java/org/apache/directory/server/FastSearchTest.java
Author: elecharny
Date: Tue Sep 25 10:15:52 2007
New Revision: 579319
URL: http://svn.apache.org/viewvc?rev=579319&view=rev
Log:
Using a different approach to have fast tests : JUnit4 allows
us to use a BeforeClass initialization. The tests are now
launching the server only once. The gain is huge.
Added:
directory/apacheds/trunk/server-unit/src/main/java/org/apache/directory/server/unit/AbstractServerFastTest.java
directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastDelITest.java
directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastSearchTest.java
Added: directory/apacheds/trunk/server-unit/src/main/java/org/apache/directory/server/unit/AbstractServerFastTest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/server-unit/src/main/java/org/apache/directory/server/unit/AbstractServerFastTest.java?rev=579319&view=auto
==============================================================================
--- directory/apacheds/trunk/server-unit/src/main/java/org/apache/directory/server/unit/AbstractServerFastTest.java (added)
+++ directory/apacheds/trunk/server-unit/src/main/java/org/apache/directory/server/unit/AbstractServerFastTest.java Tue Sep 25 10:15:52 2007
@@ -0,0 +1,422 @@
+/*
+ * 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.server.unit;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NameAlreadyBoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.directory.server.configuration.MutableServerStartupConfiguration;
+import org.apache.directory.server.core.configuration.ShutdownConfiguration;
+import org.apache.directory.server.jndi.ServerContextFactory;
+import org.apache.directory.shared.ldap.exception.LdapConfigurationException;
+import org.apache.directory.shared.ldap.ldif.Entry;
+import org.apache.directory.shared.ldap.ldif.LdifReader;
+import org.apache.directory.shared.ldap.name.LdapDN;
+import org.apache.mina.util.AvailablePortFinder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import static org.junit.Assert.assertNotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * A simple testcase for testing JNDI provider functionality.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev: 566925 $
+ */
+public abstract class AbstractServerFastTest
+{
+ private static final Logger log = LoggerFactory.getLogger( AbstractServerFastTest.class );
+ private static final List<Entry> EMPTY_LIST = Collections.unmodifiableList( new ArrayList<Entry>( 0 ) );
+ private static final String CTX_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
+
+ /** the context root */
+ protected static LdapContext root;
+
+ /** the context root for the system partition */
+ protected static LdapContext sysRoot;
+
+ /** the context root for the rootDSE */
+ protected static LdapContext rootDSE;
+
+ /** the context root for the schema */
+ protected static LdapContext schemaRoot;
+
+ /** the context root for the tests */
+ protected static LdapContext ctx;
+
+ /** flag whether to delete database files for each test or not */
+ protected static boolean doDelete = true;
+
+ protected static MutableServerStartupConfiguration configuration = new MutableServerStartupConfiguration();
+
+ protected static int port = -1;
+
+ private static long start;
+
+ protected static final String ldif = null;
+
+ protected static final String HOST = "localhost";
+ protected static final String USER = "uid=admin,ou=system";
+ protected static final String PASSWORD = "secret";
+ protected static final String BASE = "dc=example,dc=com";
+
+ /**
+ * If there is an LDIF file with the same name as the test class
+ * but with the .ldif extension then it is read and the entries
+ * it contains are added to the server. It appears as though the
+ * administor adds these entries to the server.
+ *
+ * @param verifyEntries whether or not all entry additions are checked
+ * to see if they were in fact correctly added to the server
+ * @return a list of entries added to the server in the order they were added
+ * @throws NamingException
+ */
+ protected List<Entry> loadTestLdif( boolean verifyEntries ) throws NamingException
+ {
+ InputStream in = getClass().getResourceAsStream( getClass().getSimpleName() + ".ldif" );
+
+ if ( in == null )
+ {
+ return EMPTY_LIST;
+ }
+
+ LdifReader ldifReader = new LdifReader( in );
+ List<Entry> entries = new ArrayList<Entry>();
+
+ while ( ldifReader.hasNext() )
+ {
+ Entry entry = ldifReader.next();
+ rootDSE.createSubcontext( entry.getDn(), entry.getAttributes() );
+
+ if ( verifyEntries )
+ {
+ verify( entry );
+ log.info( "Successfully verified addition of entry {}", entry.getDn() );
+ }
+ else
+ {
+ log.info( "Added entry {} without verification", entry.getDn() );
+ }
+
+ entries.add( entry );
+ }
+
+ return entries;
+ }
+
+
+ /**
+ * Verifies that an entry exists in the directory with the
+ * specified attributes.
+ *
+ * @param entry the entry to verify
+ * @throws NamingException if there are problems accessing the entry
+ */
+ protected void verify( Entry entry ) throws NamingException
+ {
+ Attributes readAttributes = rootDSE.getAttributes( entry.getDn() );
+ NamingEnumeration<String> readIds = entry.getAttributes().getIDs();
+ while ( readIds.hasMore() )
+ {
+ String id = readIds.next();
+ Attribute readAttribute = readAttributes.get( id );
+ Attribute origAttribute = entry.getAttributes().get( id );
+
+ for ( int ii = 0; ii < origAttribute.size(); ii++ )
+ {
+ if ( ! readAttribute.contains( origAttribute.get( ii ) ) )
+ {
+ log.error( "Failed to verify entry addition of {}. {} attribute in original " +
+ "entry missing from read entry.", entry.getDn(), id );
+ throw new AssertionFailedError( "Failed to verify entry addition of " + entry.getDn() );
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Common code to get an initial context via a simple bind to the
+ * server over the wire using the SUN JNDI LDAP provider. Do not use
+ * this method until after the setUp() method is called to start the
+ * server otherwise it will fail.
+ *
+ * @return an LDAP context as the the administrator to the rootDSE
+ * @throws NamingException if the server cannot be contacted
+ */
+ protected LdapContext getWiredContext() throws NamingException
+ {
+ return getWiredContext( "uid=admin,ou=system", "secret" );
+ }
+
+
+ /**
+ * Common code to get an initial context via a simple bind to the
+ * server over the wire using the SUN JNDI LDAP provider. Do not use
+ * this method until after the setUp() method is called to start the
+ * server otherwise it will fail.
+ *
+ * @param bindPrincipalDn the DN of the principal to bind as
+ * @param password the password of the bind principal
+ * @return an LDAP context as the the administrator to the rootDSE
+ * @throws NamingException if the server cannot be contacted
+ */
+ protected LdapContext getWiredContext( String bindPrincipalDn, String password ) throws NamingException
+ {
+ Hashtable<String, String> env = new Hashtable<String, String>();
+ env.put( Context.INITIAL_CONTEXT_FACTORY, CTX_FACTORY );
+ env.put( Context.PROVIDER_URL, "ldap://localhost:" + port );
+ env.put( Context.SECURITY_PRINCIPAL, bindPrincipalDn );
+ env.put( Context.SECURITY_CREDENTIALS, password );
+ env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+ return new InitialLdapContext( env, null );
+ }
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception
+ {
+ start = System.currentTimeMillis();
+
+ doDelete( configuration.getWorkingDirectory() );
+ port = AvailablePortFinder.getNextAvailable( 1024 );
+ configuration.getLdapConfiguration().setIpPort( port );
+ configuration.setShutdownHookEnabled( false );
+
+ Hashtable<String, Object> env = new Hashtable<String, Object>( configuration.toJndiEnvironment() );
+ env.put( Context.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
+ env.put( Context.SECURITY_CREDENTIALS, "secret" );
+ env.put( Context.SECURITY_AUTHENTICATION, "simple" );
+ env.put( Context.INITIAL_CONTEXT_FACTORY, ServerContextFactory.class.getName() );
+
+ setContexts( env );
+ }
+
+
+ /**
+ * Deletes the Eve working directory.
+ */
+ protected static void doDelete( File wkdir ) throws IOException
+ {
+ if ( doDelete )
+ {
+ if ( wkdir.exists() )
+ {
+ FileUtils.deleteDirectory( wkdir );
+ }
+ if ( wkdir.exists() )
+ {
+ throw new IOException( "Failed to delete: " + wkdir );
+ }
+ }
+ }
+
+
+ /**
+ * Sets the contexts of this class taking into account the extras and overrides
+ * properties.
+ *
+ * @param env an environment to use while setting up the system root.
+ * @throws NamingException if there is a failure of any kind
+ */
+ protected static void setContexts( Hashtable<String, Object> env ) throws NamingException
+ {
+ Hashtable<String, Object> envFinal = new Hashtable<String, Object>( env );
+
+ envFinal.put( Context.PROVIDER_URL, "ou=system" );
+ root = new InitialLdapContext( envFinal, null );
+
+ envFinal.put( Context.PROVIDER_URL, "" );
+ new InitialLdapContext( envFinal, null );
+
+ envFinal.put( Context.PROVIDER_URL, "ou=schema" );
+ new InitialLdapContext( envFinal, null );
+
+
+ Hashtable<String, String> envTest = new Hashtable<String, String>();
+ envTest.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+ envTest.put( "java.naming.provider.url", "ldap://localhost:" + port + "/ou=system" );
+ envTest.put( "java.naming.security.principal", "uid=admin,ou=system" );
+ envTest.put( "java.naming.security.credentials", "secret" );
+ envTest.put( "java.naming.security.authentication", "simple" );
+ sysRoot = new InitialLdapContext( envTest, null );
+
+ envTest = new Hashtable<String, String>();
+ envTest.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+ envTest.put( "java.naming.provider.url", "ldap://localhost:" + port + "/cn=schema" );
+ envTest.put( "java.naming.security.principal", "uid=admin,ou=system" );
+ envTest.put( "java.naming.security.credentials", "secret" );
+ envTest.put( "java.naming.security.authentication", "simple" );
+ schemaRoot = new InitialLdapContext( envTest, null );
+
+ envTest = new Hashtable<String, String>();
+ envTest.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+ envTest.put( "java.naming.provider.url", "ldap://localhost:" + port + "/ou=system" );
+ envTest.put( "java.naming.security.principal", "uid=admin,ou=system" );
+ envTest.put( "java.naming.security.credentials", "secret" );
+ envTest.put( "java.naming.security.authentication", "simple" );
+
+ ctx = new InitialLdapContext( envTest, null );
+ assertNotNull( ctx );
+
+ envTest = new Hashtable<String, String>();
+ envTest.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
+ envTest.put( "java.naming.provider.url", "ldap://localhost:" + port );
+ envTest.put( "java.naming.security.principal", "uid=admin,ou=system" );
+ envTest.put( "java.naming.security.credentials", "secret" );
+ envTest.put( "java.naming.security.authentication", "simple" );
+
+ rootDSE = new InitialLdapContext( envTest, null );
+ assertNotNull( rootDSE );
+ }
+
+
+ /**
+ * Sets the system context root to null.
+ *
+ * @see junit.framework.TestCase#tearDown()
+ */
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception
+ {
+ Hashtable<String, Object> env = new Hashtable<String, Object>();
+ env.put( Context.PROVIDER_URL, "ou=system" );
+ env.put( Context.INITIAL_CONTEXT_FACTORY, "org.apache.directory.server.jndi.ServerContextFactory" );
+ env.putAll( new ShutdownConfiguration().toJndiEnvironment() );
+ env.put( Context.SECURITY_PRINCIPAL, "uid=admin,ou=system" );
+ env.put( Context.SECURITY_CREDENTIALS, "secret" );
+
+ try
+ {
+ new InitialContext( env );
+ }
+ catch ( Exception e )
+ {
+ }
+
+ root = null;
+ doDelete( configuration.getWorkingDirectory() );
+ configuration = new MutableServerStartupConfiguration();
+
+ System.out.println( "Delta : " + (System.currentTimeMillis() - start ) );
+ }
+
+
+ /**
+ * Imports the LDIF entries packaged with the Eve JNDI provider jar into
+ * the newly created system partition to prime it up for operation. Note
+ * that only ou=system entries will be added - entries for other partitions
+ * cannot be imported and will blow chunks.
+ *
+ * @throws NamingException if there are problems reading the ldif file and
+ * adding those entries to the system partition
+ */
+ protected void importLdif( InputStream in ) throws NamingException
+ {
+ try
+ {
+ Iterator<Entry> iterator = new LdifReader( in );
+
+ while ( iterator.hasNext() )
+ {
+ Entry entry = iterator.next();
+ LdapDN dn = new LdapDN( entry.getDn() );
+ rootDSE.createSubcontext( dn, entry.getAttributes() );
+ }
+ }
+ catch ( Exception e )
+ {
+ String msg = "failed while trying to parse system ldif file";
+ NamingException ne = new LdapConfigurationException( msg );
+ ne.setRootCause( e );
+ throw ne;
+ }
+ }
+
+ protected static void importLdif( LdapContext ctx, String ldif ) throws NamingException
+ {
+ try
+ {
+ LdifReader reader = new LdifReader();
+ List<Entry> entries = reader.parseLdif( ldif );
+
+ for ( Entry entry:entries )
+ {
+ try
+ {
+ LdapDN dn = new LdapDN( entry.getDn() );
+ ctx.createSubcontext( dn, entry.getAttributes() );
+ }
+ catch ( NameAlreadyBoundException nabe )
+ {
+ // Do nothing if the entry has already been injected
+ }
+ }
+ }
+ catch ( Exception e )
+ {
+ String msg = "failed while trying to parse system ldif file";
+ NamingException ne = new LdapConfigurationException( msg );
+ ne.setRootCause( e );
+ throw ne;
+ }
+ }
+
+
+ /**
+ * Inject an ldif String into the server. DN must be relative to the
+ * root.
+ */
+ protected void injectEntries( String ldif ) throws NamingException
+ {
+ LdifReader reader = new LdifReader();
+ List<Entry> entries = reader.parseLdif( ldif );
+
+ Iterator<Entry> entryIter = entries.iterator();
+
+ while ( entryIter.hasNext() )
+ {
+ Entry entry = entryIter.next();
+ rootDSE.createSubcontext( new LdapDN( entry.getDn() ), entry.getAttributes() );
+ }
+ }
+}
Added: directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastDelITest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastDelITest.java?rev=579319&view=auto
==============================================================================
--- directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastDelITest.java (added)
+++ directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastDelITest.java Tue Sep 25 10:15:52 2007
@@ -0,0 +1,152 @@
+/*
+ * 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.server;
+
+
+import javax.naming.InvalidNameException;
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+
+import org.apache.directory.server.unit.AbstractServerFastTest;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+
+/**
+ * Various del scenario tests.
+ *
+ * The tree is :
+ * ou=system
+ * |
+ * +--> cn=test
+ *
+ * Scenarios :
+ * - try to delete a non existent entry
+ * - try to delete a non valid entry
+ * - try to delete an existent entry
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev: 433031 $
+ */
+public class FastDelITest extends AbstractServerFastTest
+{
+ protected static final String ldif =
+ "dn: cn=Test, ou=system\n" +
+ "objectClass: top\n" +
+ "objectClass: person\n" +
+ "objectClass: organizationalPerson\n" +
+ "objectClass: inetOrgPerson\n" +
+ "cn: Test\n" +
+ "sn: test user\n" +
+ "description: a test for delete\n";
+
+
+ /**
+ * Performs a single level search from ou=system base and
+ * returns the set of DNs found.
+ */
+ private NamingEnumeration<SearchResult> search( String name ) throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ return ctx.search( name, "(objectclass=*)", controls );
+ }
+
+ /**
+ * This method is called before each test, we don't really care if
+ * the data are loaded twice, we just don't care about the exception
+ */
+ @Before
+ public void setUp() throws NamingException
+ {
+ try
+ {
+ importLdif( rootDSE, ldif );
+ }
+ catch ( NamingException ne )
+ {
+ // Do nothing
+ }
+ }
+
+ /**
+ * Try to delete an inexistant name.
+ */
+ @Test public void testDeleteInvalidName() throws NamingException
+ {
+ try
+ {
+ ctx.destroySubcontext( "cn=This does not exist" );
+ fail("deletion should fail");
+ }
+ catch ( Exception e)
+ {
+ assertTrue( e instanceof InvalidNameException );
+ }
+ }
+
+ /**
+ * Try to delete an invalid name.
+ */
+ @Test public void testDeleteNotExistantName() throws NamingException
+ {
+ try
+ {
+ ctx.destroySubcontext( "This does not exist" );
+ fail("deletion should fail");
+ }
+ catch ( Exception e)
+ {
+ assertTrue( e instanceof InvalidNameException );
+ }
+ }
+
+ /**
+ * Try to delete an existing entry with invalid DN. We expect to receive a
+ * LdapNameNotFoundException
+ */
+ @Test public void testDeleteExistingName()
+ {
+ try
+ {
+ ctx.destroySubcontext( "cn=test" );
+ }
+ catch ( NamingException e)
+ {
+ fail( "The deleton should have been successfull" );
+ }
+
+ try
+ {
+ NamingEnumeration<SearchResult> results = search( "cn=test, ou=system" );
+ assertFalse( results.hasMore() );
+ }
+ catch ( NamingException e)
+ {
+ assertTrue( e instanceof NameNotFoundException );
+ }
+ }
+}
Added: directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastSearchTest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastSearchTest.java?rev=579319&view=auto
==============================================================================
--- directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastSearchTest.java (added)
+++ directory/apacheds/trunk/server-unit/src/test/java/org/apache/directory/server/FastSearchTest.java Tue Sep 25 10:15:52 2007
@@ -0,0 +1,1194 @@
+/*
+ * 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.server;
+
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.NameNotFoundException;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.Control;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.directory.server.core.subtree.SubentryService;
+import org.apache.directory.server.unit.AbstractServerFastTest;
+import org.apache.directory.shared.ldap.constants.SchemaConstants;
+import org.apache.directory.shared.ldap.message.AttributeImpl;
+import org.apache.directory.shared.ldap.message.AttributesImpl;
+import org.apache.directory.shared.ldap.message.SubentriesControl;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertNull;
+
+
+/**
+ * Test case for Search operation. It's using JUnit 4 capabilities,
+ * so the server is only launched once for the whole test case, but
+ * the ldif file is loaded for each test.
+ *
+ * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
+ * @version $Rev: 569048 $
+ */
+public class FastSearchTest extends AbstractServerFastTest
+{
+ static final String ldif =
+ "dn: cn=Tori Amos, ou=system\n" +
+ "objectClass: top\n" +
+ "objectClass: person\n" +
+ "objectClass: organizationalPerson\n" +
+ "objectClass: inetOrgPerson\n" +
+ "cn: Tori Amos\n" +
+ "sn: Amos\n" +
+ "description: an American singer-songwriter\n" +
+ "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAXQ\n" +
+ " 3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw5M\n" +
+ " zg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2NjY\n" +
+ " 2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABAwEiA\n" +
+ " AIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABUBA\n" +
+ " QEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AigC14\n" +
+ " //Z\n" +
+ "\n" +
+ "dn: cn=Rolling-Stones, ou=system\n" +
+ "objectClass: top\n" +
+ "objectClass: person\n" +
+ "objectClass: organizationalPerson\n" +
+ "objectClass: inetOrgPerson\n" +
+ "cn: Rolling-Stones\n" +
+ "sn: Jagger\n" +
+ "description: an American singer-songwriter\n" +
+ "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAXQ\n" +
+ " 3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw5M\n" +
+ " zg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2NjY\n" +
+ " 2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABAwEiA\n" +
+ " AIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABUBA\n" +
+ " QEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AigC14\n" +
+ " //Z\n" +
+ "\n" +
+ "dn: cn=Kate Bush, ou=system\n" +
+ "objectClass: top\n" +
+ "objectClass: person\n" +
+ "objectClass: organizationalPerson\n" +
+ "objectClass: inetOrgPerson\n" +
+ "cn: Kate Bush\n" +
+ "sn: Bush\n" +
+ "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAXQ\n" +
+ " 3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw5M\n" +
+ " zg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2NjY\n" +
+ " 2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABAwEiA\n" +
+ " AIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABUBA\n" +
+ " QEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AigC14\n" +
+ " //Z\n" +
+ "\n" +
+ "dn: cn=Heather Nova, ou=system\n" +
+ "objectClass: top\n" +
+ "objectClass: person\n" +
+ "cn: Heather Nova\n" +
+ "sn: Nova\n" +
+ "\n" +
+ "dn: sn=Ferry, ou=system\n" +
+ "objectClass: top\n" +
+ "objectClass: person\n" +
+ "objectClass: organizationalPerson\n" +
+ "objectClass: inetOrgPerson\n" +
+ "cn: Bryan Ferry\n" +
+ "sn: Ferry\n" +
+ "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAXQ\n" +
+ " 3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw5M\n" +
+ " zg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2NjY\n" +
+ " 2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABAwEiA\n" +
+ " AIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABUBA\n" +
+ " QEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AigC14\n" +
+ " //Z\n" +
+ "\n" +
+ "dn: cn=Kate Bush+sn=Bush, ou=system\n" +
+ "objectClass: top\n" +
+ "objectClass: person\n" +
+ "objectClass: organizationalPerson\n" +
+ "objectClass: inetOrgPerson\n" +
+ "cn: Kate Bush\n" +
+ "sn: Bush\n" +
+ "jpegPhoto:: /9j/4AAQSkZJRgABAQEASABIAAD/4QAWRXhpZgAATU0AKgAAAAgAAAAAAAD//gAXQ\n" +
+ " 3JlYXRlZCB3aXRoIFRoZSBHSU1Q/9sAQwAQCwwODAoQDg0OEhEQExgoGhgWFhgxIyUdKDozPTw5M\n" +
+ " zg3QEhcTkBEV0U3OFBtUVdfYmdoZz5NcXlwZHhcZWdj/9sAQwEREhIYFRgvGhovY0I4QmNjY2NjY\n" +
+ " 2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2Nj/8AAEQgAAQABAwEiA\n" +
+ " AIRAQMRAf/EABUAAQEAAAAAAAAAAAAAAAAAAAAF/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/EABUBA\n" +
+ " QEAAAAAAAAAAAAAAAAAAAUG/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AigC14\n" +
+ " //Z\n";
+
+
+ public static final String RDN = "cn=Tori Amos";
+ public static final String RDN2 = "cn=Rolling-Stones";
+ public static final String PERSON_DESCRIPTION = "an American singer-songwriter";
+ private static final String HEATHER_RDN = "cn=Heather Nova";
+ private static final String KATE_BUSH_RDN = "cn=Kate Bush";
+ private static final String filter = "(objectclass=*)";
+
+ private static final byte[] jpeg = new byte[]
+ {
+ (byte)0xff, (byte)0xd8, (byte)0xff, (byte)0xe0, 0x00, 0x10, 0x4a, 0x46,
+ 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48,
+ 0x00, 0x48, 0x00, 0x00, (byte)0xff, (byte)0xe1, 0x00, 0x16,
+ 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d,
+ 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, (byte)0xff, (byte)0xfe, 0x00, 0x17,
+ 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20,
+ 0x77, 0x69, 0x74, 0x68, 0x20, 0x54, 0x68, 0x65,
+ 0x20, 0x47, 0x49, 0x4d, 0x50, (byte)0xff, (byte)0xdb, 0x00,
+ 0x43, 0x00, 0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a,
+ 0x10, 0x0e, 0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13,
+ 0x18, 0x28, 0x1a, 0x18, 0x16, 0x16, 0x18, 0x31,
+ 0x23, 0x25, 0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c,
+ 0x39, 0x33, 0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e,
+ 0x40, 0x44, 0x57, 0x45, 0x37, 0x38, 0x50, 0x6d,
+ 0x51, 0x57, 0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e,
+ 0x4d, 0x71, 0x79, 0x70, 0x64, 0x78, 0x5c, 0x65,
+ 0x67, 0x63, (byte)0xff, (byte)0xdb, 0x00, 0x43, 0x01, 0x11,
+ 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a, 0x1a,
+ 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+ 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, (byte)0xff,
+ (byte)0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x01,
+ 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03,
+ 0x11, 0x01, (byte)0xff, (byte)0xc4, 0x00, 0x15, 0x00, 0x01,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x05, (byte)0xff, (byte)0xc4, 0x00, 0x14, 0x10, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xff,
+ (byte)0xc4, 0x00, 0x15, 0x01, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x06, (byte)0xff, (byte)0xc4,
+ 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, (byte)0xff, (byte)0xda, 0x00, 0x0c,
+ 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00,
+ 0x3f, 0x00, (byte)0x8a, 0x00, (byte)0xb5, (byte)0xe3, (byte)0xff, (byte)0xd9,
+ };
+
+ /**
+ * This method is called before each test, we don't really care if
+ * the data are loaded twice, we just don't care about the exception
+ */
+ @Before
+ public void loadLdif()
+ {
+ try
+ {
+ importLdif( rootDSE, ldif );
+ }
+ catch ( NamingException ne )
+ {
+ // Do nothing
+ }
+ }
+
+
+ /**
+ * Creation of required attributes of a person entry.
+ */
+ private Attributes getPersonAttributes( String sn, String cn )
+ {
+ Attributes attributes = new AttributesImpl();
+ Attribute attribute = new AttributeImpl( "objectClass" );
+ attribute.add( "top" );
+ attribute.add( "person" );
+ attribute.add( "organizationalPerson" );
+ attribute.add( "inetOrgPerson" );
+ attributes.put( attribute );
+ attributes.put( "cn", cn );
+ attributes.put( "sn", sn );
+ attributes.put( "jpegPhoto", jpeg );
+
+ return attributes;
+ }
+
+ private void checkForAttributes( Attributes attrs, String[] attrNames )
+ {
+ for ( int i = 0; i < attrNames.length; i++ )
+ {
+ String attrName = attrNames[i];
+
+ assertNotNull( "Check if attr " + attrName + " is present", attrs.get( attrNames[i] ) );
+ }
+ }
+
+
+ /**
+ * Performs a single level search from ou=system base and
+ * returns the set of DNs found.
+ */
+ private Set<String> search( LdapContext context, String filter ) throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+
+ try
+ {
+ NamingEnumeration ii = context.search( "", filter, controls );
+
+ // collect all results
+ HashSet<String> results = new HashSet<String>();
+
+ while ( ii.hasMore() )
+ {
+ SearchResult result = ( SearchResult ) ii.next();
+ results.add( result.getName() );
+ }
+
+ return results;
+ }
+ catch ( Exception e )
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+
+ @Test
+ public void testDirserver635() throws NamingException
+ {
+ // -------------------------------------------------------------------
+ Set<String> results = search( ctx, "(|(cn=Kate*)(cn=Tori*))" );
+ assertEquals( "returned size of results", 3, results.size() );
+ assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
+ assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
+ assertTrue( "contains cn=Kate Bush+sn=Bush", results.contains( "cn=Kate Bush+sn=Bush" ) );
+
+ // -------------------------------------------------------------------
+ results = search( ctx, "(|(cn=*Amos)(cn=Kate*))" );
+ assertEquals( "returned size of results", 3, results.size() );
+ assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
+ assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
+ assertTrue( "contains cn=Kate Bush+sn=Bush", results.contains( "cn=Kate Bush+sn=Bush" ) );
+
+ // -------------------------------------------------------------------
+ results = search( ctx, "(|(cn=Kate Bush)(cn=Tori*))" );
+ assertEquals( "returned size of results", 3, results.size() );
+ assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
+ assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
+ assertTrue( "contains cn=Kate Bush+sn=Bush", results.contains( "cn=Kate Bush+sn=Bush" ) );
+
+ // -------------------------------------------------------------------
+ results = search( ctx, "(|(cn=*Amos))" );
+ assertEquals( "returned size of results", 1, results.size() );
+ assertTrue( "contains cn=Tori Amos", results.contains( "cn=Tori Amos" ) );
+ }
+
+
+ /**
+ * Search operation with a base DN which contains a BER encoded value.
+ */
+ @Test
+ public void testSearchWithBackslashEscapedBase() throws NamingException
+ {
+ SearchControls sctls = new SearchControls();
+ sctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ String filter = "(cn=Bryan Ferry)";
+
+ // sn=Ferry with BEROctetString values
+ String base = "sn=\\46\\65\\72\\72\\79";
+
+ try
+ {
+ // Check entry
+ NamingEnumeration enm = sysRoot.search( base, filter, sctls );
+ assertTrue( enm.hasMore() );
+ while ( enm.hasMore() )
+ {
+ SearchResult sr = ( SearchResult ) enm.next();
+ Attributes attrs = sr.getAttributes();
+ Attribute sn = attrs.get( "sn" );
+ assertNotNull( sn );
+ assertTrue( sn.contains( "Ferry" ) );
+ }
+ }
+ catch ( Exception e )
+ {
+ fail( e.getMessage() );
+ }
+ }
+
+
+ /**
+ * Add a new attribute to a person entry.
+ *
+ * @throws NamingException
+ */
+ @Test
+ public void testSearchValue() throws NamingException
+ {
+ // Setting up search controls for compare op
+ SearchControls ctls = new SearchControls();
+ ctls.setReturningAttributes( new String[]
+ { "*" } ); // no attributes
+ ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+
+ // Search for all entries
+ NamingEnumeration results = sysRoot.search( RDN, "(cn=*)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*)", ctls );
+ assertTrue( results.hasMore() );
+
+ // Search for all entries ending by Amos
+ results = sysRoot.search( RDN, "(cn=*Amos)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*Amos)", ctls );
+ assertFalse( results.hasMore() );
+
+ // Search for all entries ending by amos
+ results = sysRoot.search( RDN, "(cn=*amos)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*amos)", ctls );
+ assertFalse( results.hasMore() );
+
+ // Search for all entries starting by Tori
+ results = sysRoot.search( RDN, "(cn=Tori*)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=Tori*)", ctls );
+ assertFalse( results.hasMore() );
+
+ // Search for all entries starting by tori
+ results = sysRoot.search( RDN, "(cn=tori*)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=tori*)", ctls );
+ assertFalse( results.hasMore() );
+
+ // Search for all entries containing ori
+ results = sysRoot.search( RDN, "(cn=*ori*)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*ori*)", ctls );
+ assertFalse( results.hasMore() );
+
+ // Search for all entries containing o and i
+ results = sysRoot.search( RDN, "(cn=*o*i*)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*o*i*)", ctls );
+ assertTrue( results.hasMore() );
+
+ // Search for all entries containing o, space and o
+ results = sysRoot.search( RDN, "(cn=*o* *o*)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*o* *o*)", ctls );
+ assertFalse( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*o*-*o*)", ctls );
+ assertTrue( results.hasMore() );
+
+ // Search for all entries starting by To and containing A
+ results = sysRoot.search( RDN, "(cn=To*A*)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=To*A*)", ctls );
+ assertFalse( results.hasMore() );
+
+ // Search for all entries ending by os and containing ri
+ results = sysRoot.search( RDN, "(cn=*ri*os)", ctls );
+ assertTrue( results.hasMore() );
+
+ results = sysRoot.search( RDN2, "(cn=*ri*os)", ctls );
+ assertFalse( results.hasMore() );
+ }
+
+ /**
+ * Search operation with a base DN with quotes
+ */
+ @Test
+ public void testSearchWithQuotesInBase() throws NamingException {
+
+ SearchControls sctls = new SearchControls();
+ sctls.setSearchScope(SearchControls.OBJECT_SCOPE);
+ String filter = "(cn=Tori Amos)";
+
+ // sn="Kylie Minogue" (with quotes)
+ String base = "cn=\"Tori Amos\"";
+
+ try {
+ // Check entry
+ NamingEnumeration enm = sysRoot.search( base, filter, sctls );
+ assertTrue( enm.hasMore() );
+
+ while ( enm.hasMore() ) {
+ SearchResult sr = (SearchResult) enm.next();
+ Attributes attrs = sr.getAttributes();
+ Attribute sn = attrs.get("sn");
+ assertNotNull(sn);
+ assertTrue( sn.contains( "Amos" ) );
+ }
+ } catch (Exception e) {
+ fail( e.getMessage() );
+ }
+ }
+
+
+ /**
+ * Tests for <a href="http://issues.apache.org/jira/browse/DIRSERVER-645">
+ * DIRSERVER-645<\a>: Wrong search filter evaluation with AND
+ * operator and undefined operands.
+ */
+ @Test
+ public void testUndefinedAvaInBranchFilters() throws Exception
+ {
+ // -------------------------------------------------------------------
+ Set<String> results = search( ctx, "(|(sn=Bush)(numberOfOctaves=4))" );
+ assertEquals( "returned size of results", 2, results.size() );
+ assertTrue( "contains cn=Kate Bush", results.contains( "cn=Kate Bush" ) );
+ assertTrue( "contains cn=Kate Bush+sn=Bush", results.contains( "cn=Kate Bush+sn=Bush" ) );
+
+ // if numberOfOctaves is undefined then this whole filter is undefined
+ results = search( sysRoot, "(&(sn=Bush)(numberOfOctaves=4))" );
+ assertEquals( "returned size of results", 0, results.size() );
+ }
+
+
+ @Test
+ public void testSearchSchema() throws Exception
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ controls.setReturningAttributes( new String[] { "objectClasses" } );
+
+ NamingEnumeration results = schemaRoot.search( "", "objectClass=subschema", controls );
+ assertTrue( results.hasMore() );
+ SearchResult result = ( SearchResult ) results.next();
+ assertNotNull( result );
+ assertFalse( results.hasMore() );
+
+ NamingEnumeration attrs = result.getAttributes().getAll();
+
+ while ( attrs.hasMoreElements() )
+ {
+ Attribute attr = (Attribute)attrs.next();
+ String ID = attr.getID();
+ assertEquals( "objectClasses", ID );
+ }
+
+ assertNotNull( result.getAttributes().get( "objectClasses" ) );
+ assertEquals( 1, result.getAttributes().size() );
+ }
+
+
+ /**
+ * Creates an access control subentry under ou=system whose subtree covers
+ * the entire naming context.
+ *
+ * @param cn the common name and rdn for the subentry
+ * @param subtree the subtreeSpecification for the subentry
+ * @param aciItem the prescriptive ACI attribute value
+ * @throws NamingException if there is a problem creating the subentry
+ */
+ private void createAccessControlSubentry( String cn, String subtree, String aciItem ) throws NamingException
+ {
+ // modify ou=system to be an AP for an A/C AA if it is not already
+ Attributes ap = ctx.getAttributes( "", new String[] { "administrativeRole" } );
+ Attribute administrativeRole = ap.get( "administrativeRole" );
+
+ if ( administrativeRole == null || !administrativeRole.contains( SubentryService.AC_AREA ) )
+ {
+ Attributes changes = new AttributesImpl( "administrativeRole", SubentryService.AC_AREA, true );
+ ctx.modifyAttributes( "", DirContext.ADD_ATTRIBUTE, changes );
+ }
+
+ // now add the A/C subentry below ou=system
+ Attributes subentry = new AttributesImpl( "cn", cn, true );
+ Attribute objectClass = new AttributeImpl( "objectClass" );
+ subentry.put( objectClass );
+ objectClass.add( "top" );
+ objectClass.add( "subentry" );
+ objectClass.add( "accessControlSubentry" );
+ subentry.put( "subtreeSpecification", subtree );
+ subentry.put( "prescriptiveACI", aciItem );
+ ctx.createSubcontext( "cn=" + cn, subentry );
+ }
+
+ /**
+ * Creates an access control subentry under ou=system whose subtree covers
+ * the entire naming context.
+ *
+ * @param cn the common name and rdn for the subentry
+ * @param subtree the subtreeSpecification for the subentry
+ * @param aciItem the prescriptive ACI attribute value
+ * @throws NamingException if there is a problem creating the subentry
+ */
+ private void removeAccessControlSubentry( String cn ) throws NamingException
+ {
+ // modify ou=system to be an AP for an A/C AA if it is not already
+ Attributes ap = ctx.getAttributes( "", new String[] { "administrativeRole" } );
+ Attribute administrativeRole = ap.get( "administrativeRole" );
+
+ if ( administrativeRole != null && administrativeRole.contains( SubentryService.AC_AREA ) )
+ {
+ Attribute administrativeRoleAttr = new AttributeImpl( "administrativeRole" );
+
+ Attributes changes = new AttributesImpl();
+ changes.put( administrativeRoleAttr );
+ ctx.modifyAttributes( "", DirContext.REMOVE_ATTRIBUTE, changes );
+ }
+
+ ctx.destroySubcontext( "cn=" + cn );
+ ctx.setRequestControls( new Control[] {} );
+ }
+
+ /**
+ * Test case to demonstrate DIRSERVER-705 ("object class top missing in search
+ * result, if scope is base and attribute objectClass is requested explicitly").
+ */
+ @Test
+ public void testAddWithObjectclasses() throws NamingException
+ {
+
+ SearchControls ctls = new SearchControls();
+ ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ ctls.setReturningAttributes( new String[]
+ { "objectclass" } );
+ String filter = "(objectclass=*)";
+
+ NamingEnumeration result = ctx.search( HEATHER_RDN, filter, ctls );
+
+ if ( result.hasMore() )
+ {
+ SearchResult entry = ( SearchResult ) result.next();
+ Attributes heatherReloaded = entry.getAttributes();
+ Attribute loadedOcls = heatherReloaded.get( "objectClass" );
+ assertNotNull( loadedOcls );
+ assertTrue( loadedOcls.contains( "person" ) );
+ assertTrue( loadedOcls.contains( "top" ) );
+ }
+ else
+ {
+ fail( "entry " + HEATHER_RDN + " not found" );
+ }
+ }
+
+
+ /**
+ * Test case to demonstrate DIRSERVER-705 ("object class top missing in search
+ * result, if scope is base and attribute objectClass is requested explicitly").
+ */
+ @Test
+ public void testAddWithMissingObjectclasses() throws NamingException
+ {
+ SearchControls ctls = new SearchControls();
+ ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ ctls.setReturningAttributes( new String[]
+ { "objectclass" } );
+ String filter = "(objectclass=*)";
+
+ NamingEnumeration result = ctx.search( KATE_BUSH_RDN, filter, ctls );
+
+ if ( result.hasMore() )
+ {
+ SearchResult entry = ( SearchResult ) result.next();
+ Attributes kateReloaded = entry.getAttributes();
+ Attribute loadedOcls = kateReloaded.get( "objectClass" );
+ assertNotNull( loadedOcls );
+ assertTrue( loadedOcls.contains( "top" ) );
+ assertTrue( loadedOcls.contains( "person" ) );
+ assertTrue( loadedOcls.contains( "organizationalPerson" ) );
+
+ }
+ else
+ {
+ fail( "entry " + KATE_BUSH_RDN + " not found" );
+ }
+ }
+
+
+ /**
+ * Create a person entry with multivalued RDN and check its content. This
+ * testcase was created to demonstrate DIRSERVER-628.
+ */
+ @Test
+ public void testMultiValuedRdnContent() throws NamingException
+ {
+ SearchControls sctls = new SearchControls();
+ sctls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ String filter = "(sn=Bush)";
+ String base = "";
+
+ NamingEnumeration enm = ctx.search( base, filter, sctls );
+
+ while ( enm.hasMore() )
+ {
+ SearchResult sr = ( SearchResult ) enm.next();
+ Attribute cn = sr.getAttributes().get( "cn" );
+ assertNotNull( cn );
+ assertTrue( cn.contains( "Kate Bush" ) );
+ Attribute sn = sr.getAttributes().get( "sn" );
+ assertNotNull( sn );
+ assertTrue( sn.contains( "Bush" ) );
+ }
+ }
+
+
+ /**
+ * Create a person entry with multivalued RDN and check its name.
+ */
+ @Test
+ public void testMultiValuedRdnName() throws NamingException
+ {
+ //Attributes attrs = getPersonAttributes( "Bush", "Kate Bush" );
+ String rdn = "cn=Kate Bush+sn=Bush";
+ String nameInNamespace = "cn=Kate Bush+sn=Bush,ou=system";
+
+ SearchControls sctls = new SearchControls();
+ sctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ String filter = "(sn=Bush)";
+ String base = rdn;
+
+ NamingEnumeration enm = ctx.search( base, filter, sctls );
+
+ if ( enm.hasMore() )
+ {
+ SearchResult sr = ( SearchResult ) enm.next();
+ assertNotNull( sr );
+ assertEquals( "Name in namespace", nameInNamespace, sr.getNameInNamespace() );
+ }
+ else
+ {
+ fail( "Entry not found:" + nameInNamespace );
+ }
+ }
+
+ @Test
+ public void testSearchJpeg() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ NamingEnumeration res = ctx.search( "", "(cn=Tori*)", controls );
+
+ // collect all results
+ while ( res.hasMore() )
+ {
+ SearchResult result = ( SearchResult ) res.next();
+
+ Attributes attrs = result.getAttributes();
+
+ NamingEnumeration all = attrs.getAll();
+
+ while ( all.hasMoreElements() )
+ {
+ Attribute attr = (Attribute)all.next();
+
+ if ( "jpegPhoto".equalsIgnoreCase( attr.getID() ) )
+ {
+ byte[] jpegVal = (byte[])attr.get();
+
+ assertTrue( Arrays.equals( jpegVal, jpeg ) );
+ }
+ }
+ }
+ }
+
+ @Test
+ public void subentryControl1() throws Exception
+ {
+ // create a real access control subentry
+ createAccessControlSubentry( "anyBodyAdd", "{}",
+ "{ identificationTag \"addAci\", precedence 14, "
+ + "authenticationLevel none, itemOrUserFirst userFirst: { userClasses { allUsers }, "
+ + "userPermissions { { protectedItems {entry, allUserAttributeTypesAndValues}, "
+ + "grantsAndDenials { grantAdd, grantBrowse } } } } }"
+ );
+
+ // prepare the subentry control to make the subentry visible
+ SubentriesControl control = new SubentriesControl();
+ control.setVisibility( true );
+ Control[] reqControls = new Control[] { control };
+ SearchControls searchControls = new SearchControls();
+ searchControls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+
+ ctx.setRequestControls( reqControls );
+ NamingEnumeration enm = ctx.search( "", "(objectClass=*)", searchControls );
+ Set<String> results = new HashSet<String>();
+
+ while ( enm.hasMore() )
+ {
+ SearchResult result = ( SearchResult ) enm.next();
+ results.add( result.getName() );
+ }
+
+ assertEquals( "expected results size of", 1, results.size() );
+ assertTrue( results.contains( "cn=anyBodyAdd" ) );
+
+ removeAccessControlSubentry( "anyBodyAdd" );
+ }
+
+ @Test
+ public void testSearchOID() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ NamingEnumeration res = ctx.search( "", "(2.5.4.3=Tori*)", controls );
+
+ // ensure that the entry "cn=Tori Amos" was found
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ String rdn = result.getName();
+
+ // ensure that the entry "cn=Tori Amos" was found
+ assertEquals( "cn=Tori Amos", rdn );
+
+ // ensure that no other value was found
+ assertFalse( res.hasMore() );
+ }
+
+ @Test
+ public void testSearchAttrCN() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"cn"} );
+
+ NamingEnumeration res = ctx.search( "", "(commonName=Tori*)", controls );
+
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // ensure the one and only attribute is "cn"
+ assertEquals( 1, attrs.size() );
+ assertNotNull( attrs.get("cn") );
+ assertEquals( 1, attrs.get("cn").size() );
+ assertEquals( "Tori Amos", (String)attrs.get("cn").get() );
+ }
+
+ @Test
+ public void testSearchAttrName() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"name"} );
+
+ NamingEnumeration res = ctx.search( "", "(commonName=Tori*)", controls );
+
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // ensure that "cn" and "sn" are returned
+ assertEquals( 2, attrs.size() );
+ assertNotNull( attrs.get("cn") );
+ assertEquals( 1, attrs.get("cn").size() );
+ assertEquals( "Tori Amos", (String)attrs.get("cn").get() );
+ assertNotNull( attrs.get("sn") );
+ assertEquals( 1, attrs.get("sn").size() );
+ assertEquals( "Amos", (String)attrs.get("sn").get() );
+ }
+
+ @Test
+ public void testSearchAttrCommonName() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"commonName"} );
+
+ NamingEnumeration res = ctx.search( "", "(commonName=Tori*)", controls );
+
+ assertTrue( res.hasMore() );
+
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // requested attribute was "commonName", but ADS returns "cn".
+ // Other servers do the following:
+ // - OpenLDAP: also return "cn"
+ // - Siemens DirX: return "commonName"
+ // - Sun Directory 5.2: return "commonName"
+ // ensure the one and only attribute is "cn"
+ assertEquals( 1, attrs.size() );
+ assertNotNull( attrs.get("cn") );
+ assertEquals( 1, attrs.get("cn").size() );
+ assertEquals( "Tori Amos", (String)attrs.get("cn").get() );
+ }
+
+ @Test
+ public void testSearchAttrOID() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"2.5.4.3"} );
+
+ NamingEnumeration res = ctx.search( "", "(commonName=Tori*)", controls );
+
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // requested attribute was "2.5.4.3", but ADS returns "cn".
+ // Other servers do the following:
+ // - OpenLDAP: also return "cn"
+ // - Siemens DirX: also return "cn"
+ // - Sun Directory 5.2: return "2.5.4.3"
+ // ensure the one and only attribute is "cn"
+ assertEquals( 1, attrs.size() );
+ assertNotNull( attrs.get("cn") );
+ assertEquals( 1, attrs.get("cn").size() );
+ assertEquals( "Tori Amos", (String)attrs.get("cn").get() );
+ }
+
+
+ @Test
+ public void testSearchAttrC_L() throws NamingException
+ {
+ // create administrative area
+ Attributes aaAttrs = new AttributesImpl();
+ Attribute aaObjectClass = new AttributeImpl( "objectClass" );
+ aaObjectClass.add( "top" );
+ aaObjectClass.add( "organizationalUnit" );
+ aaObjectClass.add( "extensibleObject" );
+ aaAttrs.put( aaObjectClass );
+ aaAttrs.put( "ou", "Collective Area" );
+ aaAttrs.put( "administrativeRole", "collectiveAttributeSpecificArea" );
+ DirContext aaCtx = ctx.createSubcontext( "ou=Collective Area", aaAttrs );
+
+ // create subentry
+ Attributes subentry = new AttributesImpl();
+ Attribute objectClass = new AttributeImpl( "objectClass" );
+ objectClass.add( "top" );
+ objectClass.add( SchemaConstants.SUBENTRY_OC );
+ objectClass.add( "collectiveAttributeSubentry" );
+ subentry.put( objectClass );
+ subentry.put( "c-l", "Munich" );
+ subentry.put( "cn", "Collective Subentry" );
+ subentry.put( "subtreeSpecification", "{ }" );
+ aaCtx.createSubcontext( "cn=Collective Subentry", subentry );
+
+ // create real enty
+ Attributes attributes = this.getPersonAttributes( "Bush", "Kate Bush" );
+ aaCtx.createSubcontext( "cn=Kate Bush", attributes );
+
+ // search
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"c-l" } );
+
+ NamingEnumeration res = aaCtx.search( "", "(cn=Kate Bush)", controls );
+
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // ensure the one and only attribute is "c-l"
+ assertEquals( 1, attrs.size() );
+ assertNotNull( attrs.get("c-l") );
+ assertEquals( 1, attrs.get("c-l").size() );
+ assertEquals( "Munich", (String)attrs.get("c-l").get() );
+ }
+
+ @Test
+ public void testSearchUsersAttrs() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"*"} );
+
+ NamingEnumeration res = ctx.search( "", "(commonName=Tori Amos)", controls );
+
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // ensure that all user attributes are returned
+ assertEquals( 5, attrs.size() );
+ assertNotNull( attrs.get( "cn" ) );
+ assertNotNull( attrs.get( "sn" ) );
+ assertNotNull( attrs.get( "objectClass" ) );
+ assertNotNull( attrs.get( "jpegPhoto" ) );
+ assertNotNull( attrs.get( "description" ) );
+ assertNull( attrs.get( "createtimestamp" ) );
+ assertNull( attrs.get( "creatorsname" ) );
+ }
+
+ @Test
+ public void testSearchOperationalAttrs() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"+"} );
+
+ NamingEnumeration res = ctx.search( "", "(commonName=Tori Amos)", controls );
+
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // ensure that all operational attributes are returned
+ // and no user attributes
+ assertEquals( 2, attrs.size() );
+ assertNull( attrs.get( "cn" ) );
+ assertNull( attrs.get( "sn" ) );
+ assertNull( attrs.get( "objectClass" ) );
+ assertNull( attrs.get( "jpegPhoto" ) );
+ assertNull( attrs.get( "description" ) );
+ assertNotNull( attrs.get( "createtimestamp" ) );
+ assertNotNull( attrs.get( "creatorsname" ) );
+ }
+
+ @Test
+ public void testSearchAllAttrs() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+ controls.setReturningAttributes( new String[]{"+", "*"} );
+
+ NamingEnumeration res = ctx.search( "", "(commonName=Tori Amos)", controls );
+
+ assertTrue( res.hasMore() );
+
+ SearchResult result = ( SearchResult ) res.next();
+
+ // ensure that result is not null
+ assertNotNull( result );
+
+ Attributes attrs = result.getAttributes();
+
+ // ensure that all user attributes are returned
+ assertEquals( 7, attrs.size() );
+ assertNotNull( attrs.get( "cn" ) );
+ assertNotNull( attrs.get( "sn" ) );
+ assertNotNull( attrs.get( "objectClass" ) );
+ assertNotNull( attrs.get( "jpegPhoto" ) );
+ assertNotNull( attrs.get( "description" ) );
+ assertNotNull( attrs.get( "createtimestamp" ) );
+ assertNotNull( attrs.get( "creatorsname" ) );
+ }
+
+ @Test
+ public void testSearchBadDN() throws NamingException
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+
+ try
+ {
+ ctx.search( "cn=admin", "(objectClass=*)", controls );
+ }
+ catch ( NameNotFoundException nnfe )
+ {
+ assertTrue( true );
+ }
+ }
+
+ @Test
+ public void testSearchInvalidDN() throws NamingException, Exception
+ {
+ SearchControls controls = new SearchControls();
+ controls.setSearchScope( SearchControls.ONELEVEL_SCOPE );
+
+ try
+ {
+ ctx.search( "myBadDN", "(objectClass=*)", controls );
+ fail();
+ }
+ catch ( NamingException ne )
+ {
+ assertTrue( true );
+ }
+ }
+
+ /**
+ * Check if operational attributes are present, if "+" is requested.
+ */
+ @Test
+ public void testSearchOperationalAttributes() throws NamingException
+ {
+ SearchControls ctls = new SearchControls();
+
+ ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ ctls.setReturningAttributes( new String[]
+ { "+" } );
+
+ NamingEnumeration result = ctx.search( HEATHER_RDN, filter, ctls );
+
+ if ( result.hasMore() )
+ {
+ SearchResult entry = ( SearchResult ) result.next();
+
+ String[] opAttrNames =
+ { "creatorsName", "createTimestamp" };
+
+ checkForAttributes( entry.getAttributes(), opAttrNames );
+ }
+ else
+ {
+ fail( "entry " + HEATHER_RDN + " not found" );
+ }
+
+ result.close();
+ }
+
+ /**
+ * Check if user attributes are present, if "*" is requested.
+ */
+ @Test
+ public void testSearchUserAttributes() throws NamingException
+ {
+ SearchControls ctls = new SearchControls();
+
+ ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ ctls.setReturningAttributes( new String[]
+ { "*" } );
+
+ NamingEnumeration result = ctx.search( "cn=Heather Nova", filter, ctls );
+
+ if ( result.hasMore() )
+ {
+ SearchResult entry = ( SearchResult ) result.next();
+
+ String[] userAttrNames =
+ { "objectClass", "sn", "cn" };
+
+ checkForAttributes( entry.getAttributes(), userAttrNames );
+ }
+ else
+ {
+ fail( "entry " + HEATHER_RDN + " not found" );
+ }
+
+ result.close();
+ }
+
+ /**
+ * Check if user and operational attributes are present, if both "*" and "+" are requested.
+ */
+ @Test
+ public void testSearchOperationalAndUserAttributes() throws NamingException
+ {
+ SearchControls ctls = new SearchControls();
+
+ ctls.setSearchScope( SearchControls.OBJECT_SCOPE );
+ ctls.setReturningAttributes( new String[]
+ { "+", "*" } );
+
+ String[] userAttrNames =
+ { "objectClass", "sn", "cn" };
+
+ String[] opAttrNames =
+ { "creatorsName", "createTimestamp" };
+
+ NamingEnumeration result = ctx.search( HEATHER_RDN, filter, ctls );
+
+ if ( result.hasMore() )
+ {
+ SearchResult entry = ( SearchResult ) result.next();
+ Attributes attrs = entry.getAttributes();
+
+ assertNotNull( attrs );
+
+ checkForAttributes( attrs, userAttrNames );
+ checkForAttributes( attrs, opAttrNames );
+ }
+ else
+ {
+ fail( "entry " + HEATHER_RDN + " not found" );
+ }
+
+ result.close();
+
+ ctls.setReturningAttributes( new String[]
+ { "*", "+" } );
+
+ result = ctx.search( HEATHER_RDN, filter, ctls );
+
+ if ( result.hasMore() )
+ {
+ SearchResult entry = ( SearchResult ) result.next();
+ Attributes attrs = entry.getAttributes();
+
+ assertNotNull( attrs );
+
+ checkForAttributes( attrs, userAttrNames );
+ checkForAttributes( attrs, opAttrNames );
+ }
+ else
+ {
+ fail( "entry " + HEATHER_RDN + " not found" );
+ }
+
+ result.close();
+ }
+}