Author |
Topic: LDAP Persistent Search Control -- JNDI Client |
|
SteveHB member offline |
|
posts: |
113 |
joined: |
05/31/2006 |
from: |
Mountain View, CA |
|
|
|
|
|
LDAP Persistent Search Control -- JNDI Client |
The LDAP Persistent Search Control is to alter the standard LDAP search operation so that it does not end after the initial set of entries matching the search criteria are returned. Instead, LDAP servers keep the search operation going. This provides clients and servers participating in Persistent Search with an active channel through which entries that change (and additional information about the changes that occur) can be communicated.
From the client's point of view, the control sent to server may be included in the Controls portion of an LDAPv3 SearchRequest message, as defined in Section 4.1.12 of [LDAPv3]. The structure of this control is as follows:
PersistentSearchControl ::= SEQUENCE {
controlType 2.16.840.1.113730.3.4.3,
criticality BOOLEAN DEFAULT FALSE,
controlValue persistentSearchControlValue
}
The persistentSearchControlValue is an OCTET STRING wrapping the BER-encoded version of the following SEQUENCE:
persistentSearchControlValue::= SEQUENCE {
changeTypes INTEGER,
-- add(1)|delete(2)|modify(4)|modDN(8)
changesOnly BOOLEAN,
returnECs BOOLEAN
}
|
|
|
|
|
|
|
SteveHB member offline |
|
posts: |
113 |
joined: |
05/31/2006 |
from: |
Mountain View, CA |
|
|
|
|
|
Code example of Persistent Search Control |
/**
* A code example of Persistent Search Control JNDI Client
* Note: This example has been tested to work with SunOne Directory Server
* It doesn't work with Active Directory.
*/
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.event.*;
import javax.naming.ldap.*;
import java.util.Hashtable;
import java.io.*;
public class PersistentSearchControlJndiClient
{
static final String PERSISTENT_SEARCH_OID = "2.16.840.1.113730.3.4.3";
static final String QUIT_PROMPT = "\nEnter 'q' to quit: ";
public static void main(String[] args)
{
LdapContext rootContext;
EventDirContext eventContext;
//create initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://myserevr.mycompany.com:389");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "cn=directory manager");
env.put(Context.SECURITY_CREDENTIALS, "mypassword");
env.put(Context.BATCHSIZE, "1"); //return each change as it occurs
env.put("java.naming.ldap.derefAliases", "never");
try{
// creating the initial context performs the LDAP bind
rootContext = new InitialLdapContext(env, null);
// verify that persistent search is supported, exit if not supported
if (!isPersistentSearchSupported(rootContext)){
System.out.println(
"The LDAP Server does not support persistent search");
System.exit(1);
}
// do a look up of the search base to create an EventDirContext
// to which the search listener can be added.
eventContext = (EventDirContext)rootContext.lookup("dc=mydomain,dc=com");
// create a MyEventListener instance to listen for events
MyEventListener listener = new MyEventListener("mylistener");
// Set up the search constraints
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
// Add the naming listener to the searchBase context. The interface
// methods of the listener will be called in a separate thread when
// a relevant event occurs
eventContext.addNamingListener(
"", //use the eventContext object as the target
"(objectClass=*)", //filter, include all objects
constraints, //search the subtree below the eventContext
listener); //the listener object
// loop until the user enters a q to quit.
BufferedReader in
= new BufferedReader(new InputStreamReader(System.in));
try{
String input;
while (true){
System.out.print(QUIT_PROMPT);
input = in.readLine();
if ( input.startsWith("q") || input.startsWith("Q") )
break;
}
}
catch(IOException e)
{
System.out.println(e.getMessage());
}
// Not strictly necessary since the context is closed below
eventContext.removeNamingListener(listener);
// Close context when we're done
eventContext.close();
}
catch(AuthenticationException e)
{
System.out.println(e.getMessage());
}
catch(NamingException e)
{
System.out.println(e.getMessage());
}
}
/**
* isPersistentSearchSupported
*
* Query the rootDSE to find out if the persistent search control
* is supported.
*/
static boolean isPersistentSearchSupported(
LdapContext rootContext) throws NamingException
{
SearchResult rootDSE;
NamingEnumeration searchResults;
Attributes attrs;
NamingEnumeration attrEnum;
Attribute attr;
NamingEnumeration values;
String value;
String[] attrNames = {"supportedControl"};
SearchControls searchControls = new SearchControls();
searchControls.setCountLimit(0); //0 means no limit
searchControls.setReturningAttributes(attrNames);
searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
// search for the rootDSE object
searchResults =
rootContext.search("", "(objectClass=*)", searchControls);
while (searchResults.hasMore())
{
rootDSE = (SearchResult)searchResults.next();
attrs = rootDSE.getAttributes();
attrEnum = attrs.getAll();
while (attrEnum.hasMore())
{
attr = (Attribute)attrEnum.next();
values = attr.getAll();
while (values.hasMore())
{
value = (String) values.next();
if (value.equals(PERSISTENT_SEARCH_OID))
return true;
}
}
}
return false;
}
/**
* MyEventlistener class
* An instance of this class is registered with an EventDirContext object.
* The registered instance's NamespaceChangeListener interface methods are
* called when a pertinent event occurs.
*/
static class MyEventListener implements NamespaceChangeListener,
ObjectChangeListener
{
private String id;
public MyEventListener(String id)
{
this.id = id;
}
public void objectAdded(NamingEvent evt)
{
System.out.println(
"\n\n" + id + ">>> object added event. Object Name: " +
evt.getNewBinding().getName());
System.out.print(QUIT_PROMPT);
}
public void objectRemoved(NamingEvent evt)
{
System.out.println(
"\n\n" + id + ">>> object removed event. Object Name: " +
evt.getOldBinding().getName());
System.out.print(QUIT_PROMPT);
}
public void objectRenamed(NamingEvent evt)
{
System.out.println(
"\n\n" + id + ">>> object renamed event. New name: " +
evt.getNewBinding().getName() +
" Old name: " + evt.getOldBinding().getName());
System.out.print(QUIT_PROMPT);
}
public void objectChanged(NamingEvent evt)
{
System.out.println(
"\n\n" + id + ">>> object changed event. Object name: " +
evt.getNewBinding().getName());
System.out.print(QUIT_PROMPT);
}
public void namingExceptionThrown(NamingExceptionEvent evt)
{
System.out.println(
"\n\n" + id + ">>> Listener received a naming exception");
evt.getException().printStackTrace();
System.out.print(QUIT_PROMPT);
}
}
}
|
|
|
|
|
|
|
SteveHB member offline |
|
posts: |
113 |
joined: |
05/31/2006 |
from: |
Mountain View, CA |
|
|
|
|
|
How do I know what specific attributes had been changed? |
By the nature of event notification which is used in PeersistentSearchControl or precisely EntryChangeNotificationControl, there is no spot in the control to tell the detailed information about the changes. You have to poll it to see the what has been changed.
Two ways:
Way #1 -- based on 'changeNumber'
If chamgeNumber is opted to return within EntryChangeNotification, you can poll all detailed information from the server's Changelog by the given change number.
Way #2 -- based on 'dn' If chamgeNumber is not available, you have to poll the current entry by the given event's 'dn' and then compare the entry with the previous one to tell the differences.
Here is the related code:
public void objectChanged(NamingEvent evt)
{
System.out.println(
"\n\n" + id + ">>> object changed event. Object name: " +
evt.getNewBinding().getName());
// In order to get the detailed information as to what had really changed,
// another search based on the returned name is required.
try{
EventDirContext eDir = (EventDirContext)evt.getEventContext();
// The following command will trigger additional search
// request (without PersistentSearchControl)
Attributes attrs = eDir.getAttributes(evt.getNewBinding().getName(), null);
Enumeration enu = attrs.getAll();
while(enu.hasMoreElements()){
Attribute attr = (Attribute)enu.nextElement();
System.out.println(attr.getID() + ": " + attr.get());
}
}catch(Exception e){
e.printStackTrace();
}
System.out.print(QUIT_PROMPT);
}
|
|
|
|
|
|
|
|