Paged search returning incorrect amount of results

Tried implementing the page results control to my code to get past the 1000 users limit but it only returns as many users as I've set the page size to (e.g. page size is set to 20, it will only return 20 users). If I set the page size to over 1000, it will still max out at 1000.

Here's my current code:

public void setupStupidConnection()
{   
String ldapURL = "ldap://myldapurl";
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
//set security credentials, note using simple cleartext authentication
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put("com.sun.jndi.ldap.read.timeout", "10000");

env.put(Context.SECURITY_PRINCIPAL,"username");
env.put(Context.SECURITY_CREDENTIALS,"password");

//connect to my domain controller
env.put(Context.PROVIDER_URL,ldapURL);
}

public void stupidSearch(String searchOptions)
{
String tempString = "";

try
{
    LdapContext ctx = new InitialLdapContext(env,null);
      // Activate paged results
    int pageSize = 20;
    byte[] cookie = null;
    ctx.setRequestControls(new Control[] { new PagedResultsControl(pageSize,
        Control.NONCRITICAL) });
    int total;

    //Specify the attributes to return
    String returnedAtts[]={"Name", "samAccountName", "mail", "UserAccountControl", "distinguishedName"};
    searchCtls.setReturningAttributes(returnedAtts);

    //Specify the search scope
    searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);

    //specify the LDAP search filter
    String searchFilter;
    if(!"".equals(search))
    {
        switch(searchOptions)
        {
            case "Starts with...":
               searchFilter = "(&(objectClass=user) (objectCategory=person) (CN=" + search + "*))";    
            break;
            case "Ends with...":
               searchFilter = "(&(objectClass=user) (objectCategory=person) (CN=*" + search + "))";
            break;
            case "Contains...":
               searchFilter = "(&(objectClass=user) (objectCategory=person) (CN=*" + search + "*))";
            break;
            case "Equals...":
               searchFilter = "(&(objectClass=user) (objectCategory=person) (CN=" + search + "))";
            break;
            default:
               searchFilter = "(&(objectClass=user) (objectCategory=person) (CN=" + search + "*))"; 
            break;
        }
    }
    else
    {
        searchFilter = "(&(objectClass=user) (objectCategory=person))";
    }

    //Specify the Base for the search
    String searchBase;
    switch(searchPath)
    {
        case "A":
            searchBase = "OU=Accounts1";
        break;
        case "B":
            searchBase = "OU=Accounts2";
        break;
        case "C":
            searchBase = "OU=Accounts3";
        break;
        case "D":
            searchBase = "OU=Accounts4";
        break;
        default:
            searchBase = "OU=Accounts1";
        break;
    }
do
{
    System.out.println(searchBase);
    // Search for objects using the filter
        NamingEnumeration<SearchResult> answer = ctx.search(searchBase, searchFilter, searchCtls);

        //Loop through the search results
        while (answer.hasMoreElements())
        {
            SearchResult sr = (SearchResult)answer.next();
            Attributes attrs = sr.getAttributes();

            tempString = attrs.get("distinguishedName").toString();
            tempString = tempString.substring(tempString.indexOf(",")+4);
            tempString = tempString.substring(0, tempString.indexOf(",DC="));
            String[] reverseString = tempString.split(",OU=");
            List<String> reverseList = Arrays.asList(reverseString);  
            Collections.reverse(reverseList);
            tempString = "";

            for (String reverseString1: reverseString)
            {
                tempString = tempString + "\\" + reverseString1;
            }
            tempString = tempString.substring(1);
            location.add(tempString);

            String enableCheck = attrs.get("UserAccountControl").toString().replaceAll(".*: ", "");
            String isEnabled = isActive(enableCheck).toString();
            results1.add(attrs.get("Name").toString());
           // System.out.println(attrs.get("Name").toString());
            results2.add(attrs.get("samAccountName").toString());
            results4.add(isEnabled);

            if (attrs.get("mail") != null)
            {
                results3.add(attrs.get("mail").toString());
            }
            else
            {
                results3.add("");
            }

            Control[] controls = ctx.getResponseControls();

            if (controls != null) 
            {
              for (int i = 0; i < controls.length; i++) 
              {
                if (controls[i] instanceof PagedResultsResponseControl) 
                {
                  PagedResultsResponseControl prrc = (PagedResultsResponseControl) controls[i];
                  total = prrc.getResultSize();
                  if (total != 0) 
                  {
                    System.out.println("***************** END-OF-PAGE "
                        + "(total : " + total + ") *****************\n");
                  } 
                  else 
                  {
                    System.out.println("***************** END-OF-PAGE "
                        + "(total: unknown) ***************\n");
                  }
                  cookie = prrc.getCookie();
                }
              }
            } 
            else 
            {
              System.out.println("No controls were sent from the server");
            }
            // Re-activate paged results
            ctx.setRequestControls(new Control[] { new PagedResultsControl(
                pageSize, cookie, Control.CRITICAL) });
        } 
    } while (cookie != null);
    ctx.close();
}

catch (NamingException e)
{
    System.out.println("Search error: " + e);
    System.exit(-1);
} catch (IOException ex) 
{
    Logger.getLogger(ADData.class.getName()).log(Level.SEVERE, null, ex);
}
 Logger.getLogger(ADData.class.getName()).log(Level.SEVERE, null, ex);
}

I've changed a few things to hide some company data. For every user it returns, it always outputs "No controls were sent from the server".

Any idea of what could be causing it?

Jon Skeet
people
quotationmark

You've used Control.NONCRITICAL. From the documentation:

criticality - If true then the server must honor the control and return search results as indicated by pageSize or refuse to perform the search. If false, then the server need not honor the control.

So you're basically saying you're okay with it not honouring your page size. If you really, really need the page size to be honoured, used Control.CRITICAL - but be aware that that may cause the request to fail entirely.

If you don't mind only receiving 1000 results at a time you need to use the pagination appropriately, setting the server-generated cookie to retrieve subsequent pages. It looks like you're trying to do that via the second ctx.setRequestControls call, but your code is somewhat broken:

  • Your while (cookie != null) loop isn't at the end of a do block, so I believe you've actually got code equivalent to:

    while (answer.hasMoreElements()) { ... } while (cookie != null) { ctx.close(); }

  • You're not performing the search again. I wouldn't personally expect setting the controls to automatically perform a new search. (I haven't used the LDAP API for ages, but I'd expect you to need to call ctx.search for it to have an effect.)

people

See more on this question at Stackoverflow