You can use VirtualListView
via JNDI. You have to retrieve and re-supply the 'contextID' to paginate, as follows:
static final int LIST_SIZE = 20; // Adjust to suit
@Test
public void TestVLV() throws NamingException, IOException
{
Hashtable<String,Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=XXXXXXX");
env.put(Context.SECURITY_CREDENTIALS, "YYYYYYY");
try
{
/* Create initial context with no connection request controls */
LdapContext ctx = new InitialLdapContext(env, null);
/* Sort Control is required for VLV to work */
SortKey[] sortKeys =
{
// sort by cn
new SortKey("cn", true, "caseIgnoreOrderingMatch")
};
// Note: the constructors for SortControl that take String or String[]
// as the first argument produce 'no ordering rule' errors with OpenLDAP.
SortControl sctl = new SortControl(
// "cn",
// new String[]{"cn"},
sortKeys,
Control.CRITICAL);
/* VLV that returns the first 20 answers */
VirtualListViewControl vctl =
new VirtualListViewControl(1, 0, 0, LIST_SIZE-1, Control.CRITICAL);
/* Set context's request controls */
ctx.setRequestControls(new Control[]
{
sctl,
vctl
});
int count = 0;
SearchControls sc = new SearchControls(SearchControls.SUBTREE_SCOPE, 0, 0, null, false, false);
for (;;)
{
/* Perform search */
// System.out.println("namespace="+ctx.getNameInNamespace());
// System.out.println("count limit="+sc.getCountLimit());
// System.out.println("search scope="+sc.getSearchScope());
NamingEnumeration<SearchResult> ne =
ctx.search("ou=Users,dc=xxxx,dc=com", "(objectClass={0})", new String[]{"inetOrgPerson"}, sc);
/* Enumerate search results */
while (ne.hasMore())
{
count++;
SearchResult sr = ne.next();
// System.out.println(i+": "+sr.getName());
System.out.println(count+": "+sr.getNameInNamespace());
}
ne.close();
// Get the contextID.
Control[] controls = ctx.getResponseControls();
VirtualListViewResponseControl vlvrc = null;
byte[] contextID = null;
for (int j = 0; j < controls.length; j++)
{
if (controls[j] instanceof VirtualListViewResponseControl)
{
vlvrc = (VirtualListViewResponseControl)controls[j];
contextID = vlvrc.getContextID();
System.out.println("contextID=0x"+new BigInteger(1,contextID).toString(16));
if (contextID != null)
{
vctl = new VirtualListViewControl(vlvrc.getTargetOffset()+LIST_SIZE, 0, 0, LIST_SIZE-1, Control.CRITICAL);
vctl.setContextID(contextID);
ctx.setRequestControls(new Control[]
{
sctl,
vctl
});
}
break; // there should only be one VLV response control, and we're not interested in anything else.
}
}
if (vlvrc != null && contextID != null && count < vlvrc.getListSize())
{
System.out.println("Continuing");
}
else
{
System.out.println("Finished");
break;
}
}
ctx.close();
}
finally
{
}
}
Adjust the authentication and search root and filter to suit yourself, of course.
And to test whether it is supported (although an 'unsupported critical control' exception from the above code will tell you just as well):
/**
* Is VLV Control supported?
*
* Query the rootDSE object to find out if VLV Control is supported.
* @return true if it is supported.
*/
static boolean isVLVControlSupported(LdapContext ctx)
throws NamingException
{
String[] returningAttributes =
{
"supportedControl"
};
// Fetch the supportedControl attribute of the rootDSE object.
Attributes attrs = ctx.getAttributes("", returningAttributes);
Attribute attr = attrs.get("supportedControl");
System.out.println("supportedControls="+attr);
if (attr != null)
{
// Fast way to check. add() would have been just as good. Does no damage to the DIT.
return attr.remove(VLV_CONTROL_OID);
}
return false;
}
The VirtualListViewControl
and VirtualListViewResponseControl
are part of the Sun/Oracle LDAP Booster Pack, which you can obtain via Maven as:
<dependency>
<groupId>com.sun</groupId>
<artifactId>ldapbp</artifactId>
<version>1.0</version>
<type>jar</type>
</dependency>