Example 2: Adding LDAP Address Lookup to an E-Mail Client

   

Most e-mail clients include a private address book to store addresses of people with whom users correspond on a regular basis. A useful extension of a private address book is an LDAP address lookup feature that allows users to search a centralized directory service when addressing e-mail messages. This example shows how to directory-enable the ICEMail client by adding an LDAP lookup feature.

ICEMail is a pure Java Internet e-mail client that is developed and maintained by a group of open -source developers. Tim Endres (previously with ICE Engineering; now president of SecureByDesign.com) is the original author of ICEMail. Jeff Gay is now the chief organizer for the project. The source code for ICEMail is available under the terms of the GNU General Public License. The changes shown here are based on the ICEMail 3.5.1 release. For more information, including how to obtain the source code, visit the ICEMail Web site at http://www.icemail.org.

The Integration Approach

ICEMail has an address book, but it is very basic, so it has no LDAP features and it can't be used to address e-mail messages. To provide a convenient LDAP lookup feature, the decision was made to change the ICEMail message composition window to add a Directory Lookup command to the existing Address menu. The Directory Lookup command will grab the content of the active addressing field ( To , CC , or BCC ), look it up in the directory, and replace it with a person's e-mail address, if one was found. This is an example of enabling a new feature in an existing application using LDAP.

To accomplish this tight integration, the ICEMail client source code was modified directly. Because ICEMail is a pure Java application, the modifications were written in Java. ICEMail already uses the Netscape LDAP Java SDK to support LDAP Data Interchange Format (LDIF) storage of the ICEMail address book, so the Netscape SDK was the obvious choice for implementation of the new LDAP address lookup feature. The Netscape LDAP Java SDK closely tracks the emerging Internet Engineering Task Force (IETF) Java LDAP API standard.

The End- User Experience

The original ICEMail composition window is shown in Figure 22.7. Note that a recipient's exact e-mail address must be typed into the To , CC , or BCC field when a message is being addressed.

Figure 22.7. The Original ICEMail Composition Window

There is no graphical user interface (GUI) for configuring the LDAP directory within the LDAP-enabled ICEMail application. Therefore, to enable the LDAP lookup feature, a set of lines like the ones shown in Listing 22.10 should be added to a user's icemailrc.txt properties file.

Listing 22.10 A Sample Addition to icemailrc.txt
 1. ICEMail.ldapDirectory=ldap://ldap.example.com:389/dc%3Dexample%2Cdc%3Dcom 2. ICEMail.ldapSizeLimit=25 3. ICEMail.ldapTimeLimit=30 

On line 1, the portion to the right of the equal sign ( = ) is an LDAP URL that ldap.example.com , port 389 , and URL-encoded base DN dc=example,dc=com ). Lines 2 and 3 are optional. They specify LDAP search size and time limits (in entries and seconds, respectively).

Figure 22.8 shows the revised composition window, with the Address menu pulled down to show the new lookup command. Notice that the name of the recipient (Daniel Smith) has been typed into the To field, and the Directory Lookup menu command is about to be executed.

Figure 22.8. The LDAP-Enabled ICEMail Composition Window

Figure 22.9 shows the result of the LDAP lookup: The name in the To field is replaced with Daniel Smith's e-mail address, as found in the example.com directory.

Figure 22.9. The ICEMail Composition Window after a Successful LDAP Lookup

The Source Code

ICEMail is a fairly large application with a friendly GUI built on Sun's Java Foundation Classes (JFC). ICEMail's existing ComposeFrame class was modified to add the LDAP lookup feature. This class is responsible for displaying the message composition (new message) window and interacting with the user. The source code for the ComposeFrame class is in the file java/org/icemail/mail/ComposeFrame.java .

In Listings 22.11 through 22.14, lines with an ellipsis character ( ... ) indicate a place where existing ICEMail code has been omitted to save space. Each listing includes the declaration of the class or method to which the new code belongs; this way, you can see exactly where the new code fits within the ICEMail source code. Existing code that surrounds the code changes and additions is included for context. In listings that show both existing and new code, the original ICEMail code is shown without line numbers , and the new code is shown in bold type.

Listing 22.11 shows the first code change. The two new import statements were added to gain access to the Netscape LDAP classes.

Listing 22.11 Changes to the Import Section of ICEMail's ComposeFrame Class
 ...     import org.icemail.util.RotateButton;     import org.icemail.util.StringUtilities;     import org.icemail.util.UserProperties;   1.  import netscape.ldap.*;  2.  import java.net.MalformedURLException;  /**      * This class composes and edits messages,      * allowing messages to be sent, printed, and saved. ... 

When an ICEMail ComposeFrame object is created, it reads some configuration information and stores it in member variables for later use by the class's methods . The new LDAP directory configuration information, which is stored in the icemailrc.txt properties file, is read and placed in a member variable so that it can be accessed conveniently.

Listing 22.12 shows the definition of a new member variable, named ldapDirectoryURL . Because the directory configuration can easily be represented as an LDAP URL, a variable of type LDAPUrl is used to hold it (the LDAPUrl class is part of the Netscape LDAP Java API).

Listing 22.12 An Addition to ComposeFrame 's Private Member Variables
 ...       private IActionListener   actionListener_ = new IActionListener();       private IFocusListener    focusListener_ = new IFocusListener();       private JTextComponent    currentFocus_ = null;  1.  private LDAPUrl           ldapDirectoryURL = null;  /**        * Default constructor, creating a composition frame for a new message.        */       public       ComposeFrame() { ... 

As part of the initialization of a ComposeFrame object, the ICEMail code calls a method named establishAddresses() to create the Address menu. If an LDAP directory URL is present in a user's icemailrc.txt properties file, a Directory Lookup item must be added to the Address menu. Listing 22.13 shows the required revisions to the establishAddresses() method.

Listing 22.13 Revisions to ComposeFrame 's establishAddresses() Method
 ...   private void     establishAddresses() {       JMenuItem item;       String addrListStr = UserProperties.getProperty( "addressList", null );  1.     /*   2.      * If an LDAP directory is configured, include a   3.      * "Directory Lookup" item in the Address menu.   4.      */   5.     String ldapURLStr = UserProperties.getProperty( "ldapDirectory", null );   6.     if ( ldapURLStr != null ) {   7.       try {   8.         ldapDirectoryURL = new LDAPUrl( ldapURLStr );   9.       } catch ( MalformedURLException e ) {   10.          JOptionPane.showMessageDialog( null,   11.                  "Malformed LDAP URL: " + ldapURLStr );   12.       }   13.     }   14.   15.     String[] addrList = {};   16.     if ( addrListStr != null )   17.       addrList = StringUtilities.splitString( addrListStr, ":" );   18.     if ( addrList.length < 1 && ldapDirectoryURL == null )   19.       return;   20.   21.     JMenu menu = ComponentFactory.getMenu( ICEMail.getBundle(),   22.             "Compose.Address" );   23.   24.     menuBar_.add( menu );   25.   26.     if ( ldapDirectoryURL != null ) {   27.       item = new JMenuItem( "Directory Lookup" );   28.       item.addActionListener( actionListener_ );   29.       item.setActionCommand( "LDAP:LOOKUP" );   30.       item.setAccelerator( KeyStroke.getKeyStroke( 'L',   31.             Event.CTRL_MASK ) );   32.       menu.add( item );   33.       if ( addrList.length > 0 ) menu.addSeparator();   34.     }  for ( int i = 0 ; i < addrList.length ; ++i ) {       String addrStr = UserProperties.getProperty( ("address." + addrList[i]),      null ); ... 

The code on lines 5 to 13 retrieves the ldapDirectory URL from the user's properties file and initializes the ldapDirectoryURL member variable. The code on lines 15 to 24 is identical to that in the original ICEMail source code, except some additional checks were added to test whether ldapDirectoryURL is NULL (the Address menu is not created at all if no LDAP directory and no listed addresses are configured). The code on lines 26 to 34 is new code that adds the Directory Lookup menu item and associates the LDAP:LOOKUP action with the menu item.

The actionPerformed() method is invoked to handle actions that are triggered by menu selections or similar events. Listing 22.14 shows the code that was added to support the new LDAP lookup feature.

Listing 22.14 An Addition to ComposeFrame 's actionPerformed() Method
 ...     public void     actionPerformed( ActionEvent event ) { ...       } else if ( command.equals( "LOWPRIORITY" ) ) {         headers_.setHeader( "Importance", "low" );         headers_.setHeader( "X-Priority", "5" );  1.  } else if ( command.equals( "LDAP:LOOKUP" ) ) {   2.    ldapLookup();  } else {         System.err.println( "ComposeFrame.actionPerformed() - unknown command '" +                             command + "'" );       } ... 

The LDAP searches themselves are performed by the ldapLookup() method, which is the final addition to the ComposeFrame class. Listing 22.15 shows the ldapLookup() source code.

Listing 22.15 The ComposeFrame ldapLookup() Method
 1. /*   2.  * ldapLookup: grab the contents of the current text entry field,   3.  * look it up in the configured LDAP directory, and replace the   4.  * original text with DISPLAYNAME <MAIL> or CN <MAIL>.   5.  */   6. private void   7. ldapLookup() {   8.   String            ldAttrs[] = { "cn", "displayName", "mail" };   9.   LDAPConnection    ld = null;  10.  11.   // Get text from the active text entry field  12.   if ( currentFocus_ == null ) {  13.     return;  14.   }  15.   String textToLookup = currentFocus_.getSelectedText();  16.   boolean useSelection = ( textToLookup != null );  17.   if ( !useSelection ) {  18.     textToLookup = currentFocus_.getText();  19.   }  20.   if ( textToLookup.length() <= 0 ) {  21.     return;  22.   }  23.  24.   // Create filter and execute an LDAP search  25.   String ldFilter = "(&(objectClass=person)";  26.   if ( textToLookup.indexOf( "=" ) != -1 ) {  27.     ldFilter += "(" + textToLookup + "))";  28.   } else {  29.     ldFilter += "((cn=" + textToLookup + ")"  30.             + "(uid=" + textToLookup + ")"  31.             + "(sn=" + textToLookup + ")))";  32.   }  33.  34.   try {  35.     ld = new LDAPConnection();  36.     ld.connect( ldapDirectoryURL.getHost(),  37.             ldapDirectoryURL.getPort() );  38.  39.     LDAPSearchConstraints cons = new LDAPSearchConstraints();  40.     int sizelimit = UserProperties.getProperty( "ldapSizeLimit", 100 );  41.     int timelimit = UserProperties.getProperty( "ldapTimeLimit", 60 );  42.     cons.setMaxResults( sizelimit );        // entries  43.     cons.setServerTimeLimit( timelimit );   // seconds  44.  45.     LDAPSearchResults res = ld.search(  46.             ldapDirectoryURL.getDN(),  47.             LDAPConnection.SCOPE_SUB, ldFilter, ldAttrs, false,  48.             cons );  49.  50.     // Process the search results  51.     int         count = 0;  52.     LDAPEntry   entry = null;  53.  54.     while ( res.hasMoreElements()) {  55.       try {  56.         entry = res.next();  57.         ++count;  58.       } catch ( LDAPException e ) {  59.         JOptionPane.showMessageDialog( null,  60.                 "LDAP res.next error: " + e.errorCodeToString());  61.       }  62.     }  63.  64.     if ( count <= 0 ) {  65.       JOptionPane.showMessageDialog( null,  66.               "No entries matched " + textToLookup );  67.     } else if ( count > 1 ) {  68.       JOptionPane.showMessageDialog( null,  69.               count + " entries matched " + textToLookup );  70.     } else {  71.       /*  72.        * Exactly one match... replace contents of current field  73.        * (or current selection) with:  74.        *    displayName <mail>  75.        * or:  76.        *    cn <mail>  77.        */  78.       String        newAddress;  79.       Enumeration   enum;  80.       LDAPAttribute attr = entry.getAttribute( "displayName" );  81.       if ( attr == null ) {  82.           attr = entry.getAttribute( "cn" );  83.       }  84.       if ( attr != null ) {  85.           enum = attr.getStringValues();  86.           newAddress = (String)enum.nextElement() + " ";  87.       } else {  88.           newAddress = "";  89.       }  90.  91.       attr = entry.getAttribute( "mail" );  92.       enum = attr.getStringValues();  93.       newAddress += "<" + (String)enum.nextElement() + ">";  94.       if ( useSelection ) {  95.         currentFocus_.replaceSelection( newAddress );  96.       } else {  97.         currentFocus_.setText( newAddress );  98.       }  99.     } 100. 101.   } catch ( LDAPException e ) { 102.     JOptionPane.showMessageDialog( null, 103.             "LDAP connect or search error: " 104.             + e.errorCodeToString()); 105.   } 106. 107.   // Clean up -- close the LDAP connection 108.   if ( ld != null && ld.isConnected()) { 109.     try { 110.       ld.disconnect(); 111.     } catch ( LDAPException e ) { 112.       /* ignore */ 113.     } 114.   } 115. } 

Although lengthy, the ldapLookup() code is straightforward. After some local variables are declared (lines 8 and 9), the code on lines 11 to 19 retrieves the text that is used to search and places it in a local variable named textToLookup . The text to look up is taken from the active text input field within the composition window. If any text is selected, it is used; otherwise , the entire content of the text field is used. If there is no text at all, the method returns without doing anything (lines 20 “22).

The code on lines 24 to 32 creates an LDAP search filter based on the textToLookup value. An (objectClass=person) ANDed component is always included. If the text to look up contains an equal sign, it is used directly to form the rest of the filter; otherwise, a filter that exactly matches against cn , sn , or uid is used.

The code on lines 34 “48 connects to the LDAP directory and performs one search. The LDAP host, port, and search base are obtained from the ldapDirectoryURL private member variable that was initialized by the code examined earlier (see Listing 22.13). Only three attributes are retrieved from the LDAP server: cn , displayName , and mail (as specified in the ldAttrs array). Size and time limit values are retrieved from the user's icemailrc.txt properties file; default values of 100 entries and 60 seconds, respectively, are used if the properties file does not contain values.

The code on lines 50 to 62 examines the search results and counts the number of entries returned. The code on lines 64 to 99 handles the three possible outcomes of a successful search. If no entries are found, the code on lines 64 to 66 displays a message box to inform the user. If more than one match is found, a different message box is presented to let the user know (lines 67 “69). The interesting case in which a single matching entry is found is handled by lines 70 to 99. If the entry has an e-mail address, the content of the active text entry field (or the selected text within that field) is replaced with a string formed from the entry's displayName or common name ( cn ) attribute and the mail attribute. The remainder of the code in the ldapLookup() method is concerned with error handling and cleanup.

Ideas for Improvement

Many things could be done to improve the LDAP lookup feature in ICEMail. Here are some ideas:

  • Present a list to allow the user to choose the desired entry if more than one match is found. The lack of this capability is the biggest limitation of the example code.

  • Improve the generation of search filters. As written, the code in ldapLookup() simply searches for entries whose surname , full name, or user ID exactly matches the query string, unless the user types an LDAP search filter (which most people will not know how to do). A more intelligent approach would be to examine the query string and generate different filters depending on its content. For example, a string that consists only of numbers could trigger a search by telephone number. Another approach would be to make the filter configurable by each ICEMail user.

  • Provide a GUI for setting the ldapDirectory , ldapTimeLimit , and ldapSizeLimit preferences that are stored in the ICEMail properties file. You could do this by making changes to ICEMail's Configuration class.

  • Allow entries found via LDAP to be added to the ICEMail address book.

  • Support more than one LDAP server.

  • Provide feedback to the user while the LDAP lookup is being done. Such feedback might be as simple as displaying an hourglass cursor, or as complex as displaying a dialog with a status message showing the progress of the LDAP search. For example, the dialog might show "Connecting to ldap.example.com" followed by "Executing search..." followed by "Retrieved entry 1..." and so on.

   


Understanding and Deploying LDAP Directory Services
Understanding and Deploying LDAP Directory Services (2nd Edition)
ISBN: 0672323168
EAN: 2147483647
Year: 2002
Pages: 242

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net