//Warning: This code has been marked up for HTML

/**************************************************************************
*  Novell Software Developer Kit
*
*  Copyright (C) 2002-2003 Novell, Inc. All Rights Reserved.
*
*  THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
*  USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
*  ACCOMPANYING THE SOFTWARE DEVELOPER KIT (SDK) THAT CONTAINS THIS WORK.
*  PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
*  ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
*  PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
*  DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
*  PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
*  CUSTOMERS WITH RESPECT TO THIS CODE.
*
* $name:         PSearch.java
* $version:      1.0
* $date_modified:Thr, September 11, 2000
* $owner:
* $description:  The PSearch.java example shows how to perform a
*                persistent search on a directory that supports LDAP. The
*                PSearch class performs a persistent search on the directory
*                and receives notification of any changes to entries within
*                the scope of the search's result set.
******************************************************************************/
import javax.naming.*;
import javax.naming.directory.*;
import javax.naming.event.*;
import javax.naming.ldap.*;

import java.util.Hashtable;
import java.io.*;

public class PSearch
{
   
    static final String  PERSISTENT_SEARCH_OID = "2.16.840.1.113730.3.4.3";
    static final String  QUIT_PROMPT =
                                   "\nMonitoring changes. Enter a 'q' to quit: ";

    public static void main(String[] args)
    {
        LdapContext     rootContext;
        EventDirContext eventContext;

        if (args.length != 4)
        {
            System.err.println("\n Usage:   java PSearch <host name>"
                                +"<login dn> <password> <search base>");
            System.err.println("\n Example: java PSearch ldap://Acme.com:389 "
                                +" \"cn=Admin,o=Acme\" secret" 
                                +"\n          \"ou=Sales,o=Acme\"");
            System.exit(0);
        }

        String hostURL     = args[0];
        String loginDN     = args[1];
        String password    = args[2];
        String searchBase  = args[3];
        

       //create initial context


        Hashtable env = new Hashtable();

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

       // use simple Authentication

        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.SECURITY_PRINCIPAL, loginDN);
        env.put(Context.SECURITY_CREDENTIALS, password);
        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(searchBase);

           //create a MyEventListener instance to listen for events

            MyEventListener listener = new MyEventListener("listener_1");

           // 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 object 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);
        }
    }//end class EventListener


}// end class PSearch