Saturday, July 7, 2012

How to use paging from Groovy with JNDI LDAP to access Active Directory for large resultset

I need to query the Microsoft Active Directory (AD) via LDAP, and because the result set contained more than thousands of records, in this case the client must use paging, documented on http://tools.ietf.org/html/rfc2696 fetching more record than defined as default in the AD.

I found the following good example for paging using the default Sun's Oracle's JNDI driver for LDAP in Java here: http://www.forumeasy.com/forums/thread.jsp?tid=115756126876&fid=ldapprof2&highlight=LDAP+Search+Paged+Results+Control which needed to port to Groovy. Because Groovy support only the basic syntax of Java, I made some changes. I hope it helps others' work, eg. writing a scriptlet in XWiki.

Please note, this can be compiled to .class object code, and can be run as groovy script as well. The pageSize sets the number of records what the Active Directory responds to our LDAP query:
#!/bin/env groovy

/**
 *
 * PagedResultsControlJndiClient.java/groovy
 * Sample code to demostrate how Paged Results Control works.
 *
 * Based on
 * http://www.forumeasy.com/forums/thread.jsp?tid=115756126876&fid=ldapprof2&highlight=LDAP+Search+Paged+Results+Control
 * groovyzed by Tamas Szerb
**/

import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.ldap.*;

import java.util.Hashtable;

public class PagedResultsControlJndiClient {
        static final String  PAGED_RESULT_CONTROL_OID = "1.2.840.113556.1.4.319";

        public static void main(String[] args) {
                Hashtable env = new Hashtable();

                env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

                // Note: Active Directory Server supports Paged Results Control
                //         SunOne does not supports Paged Results Control but it supports
                //         Virtual List View Control instead.
                env.put(Context.PROVIDER_URL, "ldap://myAD.mydomain.com:389");
                env.put(Context.SECURITY_AUTHENTICATION, "simple");
                env.put(Context.SECURITY_PRINCIPAL, "administrator@mydomain.com");
                env.put(Context.SECURITY_CREDENTIALS, "mypassword");

                try {
                        /* Open an LDAP connection for the provided principal and credentials */
                        LdapContext ctx = new InitialLdapContext(env, null);
                        System.out.println("Initial binding done!");

                        /* Query the server to see if the paged result control is supported */
                        if(!isPagedResultControlSupported(ctx)){
                                System.out.println("The server does not support Paged Results Control.");
                                System.exit(1);
                        }

                        /* Activate paged results */
                        int pageSize = 10;
                        byte[] cookie = null;
                        int total;

                        //ctx.setRequestControls(
                        //      new Control[]{new PagedResultsControl(pageSize, Control.CRITICAL)});
                        PagedResultsControl resultsControl = new PagedResultsControl(pageSize, Control.CRITICAL);
                        Control[] control = [resultsControl];
                        ctx.setRequestControls(control);

                        System.out.println("Paged control set!");

                        int count = 0;
                        while(true) {
                                count++;
                                System.err.println("Search loop count = " + count);

                                SearchControls ctls = new SearchControls();
                                //ctls.setSearchScope(SearchControls.ONELEVEL_SCOPE);
                                ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
                                ctls.setCountLimit(0);
                                // Perform the search
                                NamingEnumeration results = ctx.search("dc=mydomain,dc=com","(objectclass=*)", ctls);

                                try {
                                // Iterate over a batch of search results
                                        while (results != null && results.hasMore()) {
                                                // Display an entry
                                                SearchResult entry = (SearchResult)results.next();
                                                System.out.println("dn: " + entry.getName());
                                                // fetching the attribute:
                                                System.out.println("cn: " + entry.getAttributes().get("cn").get());
                                        }
                                }
                                catch(Exception pe) {
                                        System.out.println(pe.toString());  // Patial Result Exception
                                }

                                // Examine the paged results control response
                                Control[] controls = ctx.getResponseControls();
                                if(controls!=null) {
                                        for(int k = 0; k<controls.length; k++) {
                                                if(controls[k] instanceof PagedResultsResponseControl) {
                                                        PagedResultsResponseControl prrc = (PagedResultsResponseControl)controls[k];
                                                        total = prrc.getResultSize();
                                                        cookie = prrc.getCookie();
                                                }
                                                else {
                                                        // Handle other response controls (if any)
                                                }
                                        }
                                }

                                if(cookie==null) break;

                                // Re-activate paged results
                                //ctx.setRequestControls(new Control[]{
                                //new PagedResultsControl(pageSize, cookie, Control.CRITICAL)});
                                resultsControl = new PagedResultsControl(pageSize, cookie, Control.CRITICAL);
                                control = [resultsControl];
                                ctx.setRequestControls(control);
                        }

                        // Close the LDAP association
                        ctx.close();
                }
                catch (Exception e) {
                        e.printStackTrace();
                }
        }

        /**
         * Is paged result control supported?
         *
         * Query the rootDSE object to find out if the paged result control
         * is supported.
         */
        static boolean isPagedResultControlSupported(LdapContext ctx) throws NamingException {
                SearchControls ctl = new SearchControls();
                //ctl.setReturningAttributes(new String[]{"supportedControl"});
                String[] tmp = ["supportedControl"];
                ctl.setReturningAttributes(tmp);
                ctl.setSearchScope(SearchControls.OBJECT_SCOPE);

                /* search for the rootDSE object */
                NamingEnumeration results = ctx.search("", "(objectClass=*)", ctl);

                while(results.hasMore()) {
                        SearchResult entry = (SearchResult)results.next();
                        NamingEnumeration attrs = entry.getAttributes().getAll();
                        while (attrs.hasMore()) {
                                Attribute attr = (Attribute)attrs.next();
                                NamingEnumeration vals = attr.getAll();
                                while (vals.hasMore()) {
                                        String value = (String)vals.next();
                                        if(value.equals(PAGED_RESULT_CONTROL_OID))
                                        return true;
                                }
                        }
                }
                return false;
        }
}