CHAPTER 7

image

Sorting and Paging Results

In this chapter you will learn

  • The basics of LDAP controls.
  • Sorting LDAP results.
  • Paging LDAP results.

LDAP Controls

LDAP controls provide a standardized way to modify the behavior of LDAP operations. A control can be viewed simply as a message that a client sends to an LDAP server (or vice versa). Controls that are sent as part of a client request can provide additional information to the server indicating how the operation should be interpreted and executed. For example, a delete subtree control can be specified on an LDAP delete operation. Upon receiving a delete request, the default behavior of an LDAP server is to just delete the entry. However, when a delete subtree control is appended to the delete request, the server automatically deletes the entry as well as all its subordinate entries. Such controls are referred to as request controls.

It is also possible for LDAP servers to send controls as part of their response message indicating how the operation was processed. For example, an LDAP server may return a password policy control during a bind operation indicating that the client’s password has expired or will be expiring soon. Such controls sent by the server are referred to as response controls. It is possible to send any number of request or response controls along with an operation.

LDAP controls, both request and response, are made up of the following three components:

  • An Object Identifier (OID) that uniquely identifies the control. These OIDs prevent conflicts between control names and are usually defined by the vendor that creates the control. This is a required component of a control.
  • Indication whether the control is critical or non-critical for the operation. This is also a required component and can be either TRUE or FALSE.
  • Optional information specific to the control. For example, the paged control used for paging search results needs the page size to determine the number of entries to return in a page.

The formal definition of an LDAP control as specified in RFC 2251 (www.ietf.org/rfc/rfc2251.txt) is shown in Figure 7-1. This LDAP specification, however, does not define any concrete controls. Control definitions are usually provided by LDAP vendors and their support varies vastly from one server to another.

9781430263975_Fig07-01.jpg

Figure 7-1. LDAP control specification

When an LDAP server receives a control as part of an operation, its behavior is dependent on the control and its associated information. The flow chart in Figure 7-2 shows the server behavior upon receiving a request control.

9781430263975_Fig07-02.jpg

Figure 7-2. LDAP server control interaction

Some of the commonly supported LDAP controls along with their OID and description are shown in Table 7-1.

Table 7-1. Commonly Used Controls

Control Name OID Description (RFC)
Sort Control 1.2.840.113556.1.4.473 Requests the server to sort the search results before sending them to client. This is part of RFC 2891.
Paged Results Control 1.2.840.113556.1.4.319 Requests the server to return search results in pages consisting of specified number of entries. Only sequential iteration of the search results is allowed. This is defined as part of RFC 2696.
Subtree Delete Control 1.2.840.113556.1.4.805 Requests the server delete the entry and all its descendent entries.
Virtual List View Control 2.16.840.1.113730.3.4.9 This is similar to Page search results but allows client request arbitrary subsets of entries. This control is described in the Internet Drafts file VLV 04.
Password Policy Control 1.3.6.1.4.1.42.2.27.8.5.1 Server-sent control that holds information about failed operation (authentication, for example) due to password policy problems such as password needs to be reset or account has been locked or password has expired or expiring.
Manage DSA/IT Control 2.16.840.1.113730.3.4.2 Requests the server to treat “ref” attribute entries (referrals) as regular LDAP entries.
Persistent Search Control 2.16.840.1.113730.3.4.3 This control allows the client to receive notifications of changes in the LDAP server for entries that match a search criteria.

Identifying Supported Controls

Before a particular control can be used, it is important to make sure that the LDAP server you are using supports that control. The LDAP specification mandates every LDAP v3 compliant server publish all the supported controls in the supportedControl attribute of the Root DSA-Specific Entry (DSE). Thus, searching the Root DSE entry for the supportedControl attribute will list all the controls. Listing 7-1 shows the code that connects to the OpenDJ server running on port 11389 and prints the control list to the console.

Listing 7-1.

package com.inflinx.book.ldap;
 
import java.util.Properties;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
 
public class SupportedControlApplication {
 
   public void displayControls() {
 
      String ldapUrl = "ldap://localhost:11389";
      try {
         Properties environment = new Properties();
         environment.setProperty(DirContext.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
         environment.setProperty(DirContext.PROVIDER_URL,ldapUrl);
         DirContext context = new InitialDirContext(environment);
         Attributes attributes = context.getAttributes("", new String[]{"supportedcontrol"});
         Attribute supportedControlAttribute = attributes.get("supportedcontrol");
         NamingEnumeration controlOIDList = supportedControlAttribute.getAll();
         while(controlOIDList != null && controlOIDList.hasMore()) {
            System.out.println(controlOIDList.next());
         }
         context.close();
      }
      catch(NamingException e) {
         e.printStackTrace();
      }
   }
 
   public static void main(String[] args) throws NamingException {
      SupportedControlApplication supportedControlApplication = new SupportedControlApplication();
      supportedControlApplication.displayControls();
   }
}

Here is the output after running the code from Listing 7-1:

1.2.826.0.1.3344810.2.3
1.2.840.113556.1.4.1413
1.2.840.113556.1.4.319
1.2.840.113556.1.4.473
1.2.840.113556.1.4.805
1.3.6.1.1.12
1.3.6.1.1.13.1
1.3.6.1.1.13.2
1.3.6.1.4.1.26027.1.5.2
1.3.6.1.4.1.42.2.27.8.5.1
1.3.6.1.4.1.42.2.27.9.5.2
1.3.6.1.4.1.42.2.27.9.5.8
1.3.6.1.4.1.4203.1.10.1
1.3.6.1.4.1.4203.1.10.2
2.16.840.1.113730.3.4.12
2.16.840.1.113730.3.4.16
2.16.840.1.113730.3.4.17
2.16.840.1.113730.3.4.18
2.16.840.1.113730.3.4.19
2.16.840.1.113730.3.4.2
2.16.840.1.113730.3.4.3
2.16.840.1.113730.3.4.4
2.16.840.1.113730.3.4.5
2.16.840.1.113730.3.4.9

The OpenDJ installation provides a command line ldapsearch tool that can also be used for listing the supported controls. Assuming that OpenDJ is installed under c:practicalldapopendj on Windows, here is the command to get a list of supported controls:

ldapsearch --baseDN "" --searchScope base --port 11389 "(objectclass=*)" supportedControl

Figure 7-3 displays the results of running this command. Notice that in order to search Root DSE, you used the scope base and did not provide a base DN. Also, the supported control OIDs in the figure match the OIDs received after running the Java code in Listing 7-1.

9781430263975_Fig07-03.jpg

Figure 7-3. OpenDJ ldapsearch command

JNDI and Controls

The javax.naming.ldap package in the JNDI API contains support for LDAP V3-specific features such as controls and extended operations. While controls modify or augment the behavior of existing operations, extended operations allow additional operations to be defined. The UML diagram in Figure 7-4 highlights some of the important control classes in the javax.naming.ldap package.

9781430263975_Fig07-04.jpg

Figure 7-4. Java LDAP control class hierarchy

The javax.naming.ldap.Control interface provides abstraction for both request and response controls. Several implementations of this interface, such as SortControl and PagedResultsControl, are provided as part of the JDK. Additional controls, such as Virtual- ListViewControl and PasswordExpiringResponseControl, are available as part of the LDAP booster pack.

A core component in the javax.naming.ldap package is the LdapContext interface. This interface extends the javax.naming.DirContext interface and provides additional methods for performing LDAP V3 operations. The InitialLdapContext class in the javax.naming.ldap package provides a concrete implementation of this interface.

Using controls with JNDI API is a very straightforward. The code in Listing 7-2 provides the algorithm for using controls.

Listing 7-2.

   LdapContext context = new InitialLdapContext();
   Control[] requestControls = // Concrete control instance array
   context.setRequestControls(requestControls);
   /* Execute a search operation using the context*/
   context.search(parameters);
   Control[] responseControls = context.getResponseControls();
   // Analyze the response controls

In this algorithm, you start by creating instances of the controls that you would like to include in the request operation. Then you perform the operation and process the results of the operation. Finally, you analyze any response controls that the server has sent over. In the coming sections, you will look at concrete implementations of this algorithm in conjunction with sort and paging controls.

Spring LDAP and Controls

Spring LDAP does not provide access to a directory context when working with LdapTemplate’s search methods. As a result, you don’t have a way to add request controls to the context or process response controls. To address this, Spring LDAP provides a directory context processor that automates the addition and analysis of LDAP controls to a context. Listing 7-3 shows the DirContextProcessor API code.

Listing 7-3.

package org.springframework.ldap.core;
 
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
 
public interface DirContextProcessor {
   void preProcess(DirContext ctx) throws NamingException;
   void postProcess(DirContext ctx) throws NamingException;
}

Concrete implementations of the DirContextProcessor interface are passed to the LdapTemplate’s search methods. The preProcess method gets called before a search is performed. Hence, the concrete implementations will have logic in the preProcess method to add request controls to the context. The postProcess method will be called after the search execution. So, the concrete implementations will have logic in the postProcess method to read and analyze any response controls that the LDAP server would have sent.

Figure 7-5 shows the UML representation of the DirContextProcessor and all its implementations.

9781430263975_Fig07-05.jpg

Figure 7-5. DirContextProcessor class hierarchy

The    AbstractRequestControlDirContextProcessor    implements the preProcess method of the DirContextProcessor and applies a single RequestControl on an LdapContext. The AbstractRequestDirContextProcessor delegates the actual creation of the request controls to the subclasses through the createRequestControl template method.

The AbstractFallbackRequestAndResponseControlDirContextProcessor class extends the AbstractRequestControlDirContextProcessor and makes heavy use of reflection to automate DirContext processing. It performs the tasks of loading control classes, creating their instances, and applying them to the context. It also takes care of most of the post processing of the response control, delegating a template method to the subclass that does the actual value retrieval.

The PagedResultsDirContextProcessor and SortControlDirContextProcessor are used for managing paging and sorting controls. You will be looking at them in the coming sections.

Sort Control

The sort control provides a mechanism to request an LDAP server sort the results of a search operation before sending them over to the client. This control is specified in RFC 2891 (www.ietf.org/rfc/rfc2891.txt). The sort request control accepts one or more LDAP attribute names and supplies it to the server to perform the actual sorting.

Let’s look at using the sort control with plain JNDI API. Listing 7-4 shows the code for sorting all search results by their last names. You start by creating a new instance of the javax.naming.ldap.SortControl and provide it with the sn attribute indicating your intention to sort by last name. You have also indicated that this is a critical control by providing the CRITICAL flag to the same constructor.  This request control is then added to the context using the setRequestControls method and the LDAP search operation is performed. You then loop through the returned results and print them to the console. Finally, you look at the response controls. The sort response control holds the result of the sort operation. If the server failed to sort the results, you indicate this by throwing an exception.

Listing 7-4.

public void sortByLastName() {
   try {
      LdapContext context = getContext();
      Control lastNameSort = new SortControl("sn", Control.CRITICAL);
      context.setRequestControls(new Control[]{lastNameSort});
      SearchControls searchControls = new SearchControls();
      searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE);
      NamingEnumeration results = context.search("dc=inflinx,dc=com", "(objectClass=inetOrgPerson)", searchControls);
      
          /* Iterate over search results and display
      * patron entries
      */
      while (results != null && results.hasMore()) {
         SearchResult entry = (SearchResult)results.next();
         System.out.println(entry.getAttributes().get("sn") + " ( " + (entry.getName()) + " )");
      }
 
      /* Now that we have looped, we need to look at the response controls*/
      Control[] responseControls = context.getResponseControls();
      if(null != responseControls) {
         for(Control control : responseControls) {
            if(control instanceof SortResponseControl) {
               SortResponseControl sortResponseControl = (SortResponseControl) control;
               if(!sortResponseControl.isSorted()) {
                  // Sort did not happen. Indicate this with an exception
                  throw sortResponseControl.getException();
               }
            }
        }
      }
      context.close();
   }
   catch(Exception e) {
      e.printStackTrace();
   }
}
 
The output should display the sorted patrons as shown below:
sn: Aalders ( uid=patron4,ou=patrons )
sn: Aasen ( uid=patron5,ou=patrons )
sn: Abadines ( uid=patron6,ou=patrons )
sn: Abazari ( uid=patron7,ou=patrons )
sn: Abbatantuono ( uid=patron8,ou=patrons )
sn: Abbate ( uid=patron9,ou=patrons )
sn: Abbie ( uid=patron10,ou=patrons )
sn: Abbott ( uid=patron11,ou=patrons )
sn: Abdalla ( uid=patron12,ou=patrons )
......................................

Now let’s look at implementing the same sort behavior using Spring LDAP. Listing 7-5 shows the associated code. In this implementation, you start out by creating a new org.springframework.ldap.control.SortControlDirContextProcessor instance. The SortControlDirContextProcessor constructor takes the LDAP attribute name that should be used as the sort key during control creation. The next step is to create the SearchControls and a filter to limit the search. Finally, you invoke the search method, passing in the created instances along with a mapper to map the data.

Listing 7-5.

public List<String> sortByLastName() {
   DirContextProcessor scdcp = new SortControlDirContextProcessor("sn");
   SearchControls searchControls = new SearchControls();
   searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
    
   EqualsFilter equalsFilter = new EqualsFilter("objectClass", "inetOrgPerson");
    
   @SuppressWarnings("unchecked")
   ParameterizedContextMapper<String> lastNameMapper = new AbstractParameterizedContextMapper<String>() {
      @Override
      protected String doMapFromContext(DirContextOperations context) {
         return context.getStringAttribute("sn");
      }
   };
    
   List<String> lastNames = ldapTemplate.search("", equalsFilter.encode(), searchControls, lastNameMapper, scdcp);
   for (String ln : lastNames){
      System. out .println(ln);
   }
   return lastNames;
}

Upon invoking this method, you should see the following output in the console:

Aalders
Aasen
Abadines
Abazari
Abbatantuono
Abbate
Abbie
Abbott
Abdalla
Abdo
Abdollahi
Abdou
Abdul-Nour
................

Implementing Custom DirContextProcessor

As of Spring LDAP 1.3.2, SortControlDirContextProcessor can be used to sort only on one LDAP attribute. The JNDI API, however, allows you to sort on multiple attributes. Since there will be cases where you would like to sort your search results on multiple attributes, let’s implement a new DirContextProcessor that will allow you to do this in Spring LDAP.

As you have seen so far, the sort operation requires a request control and will send a response control. So the easiest way to implement this functionality is to extend the  AbstractFallbackRequestAndResponseControlDirContextProcessor. Listing 7-6 shows the initial code with empty abstract methods implementation. As you will see, you are using three instance variables to hold the state of the control. The sortKeys, as the name suggests, will hold the attribute names that you will be sorting on. The sorted and the resultCode variables will hold the information extracted from the response control.

Listing 7-6.

package com.inflinx.book.ldap.control;
 
import javax.naming.ldap.Control;
import org.springframework.ldap.control.AbstractFallbackRequestAndResponseControlDirContextProcessor;
 
public class SortMultipleControlDirContextProcessor extends AbstractFallbackRequestAndResponseControlDirContextProcessor {
 
   //The keys to sort on
   private String[] sortKeys;
    
   //Did the results actually get sorted?
   private boolean sorted;
    
   //The result code of the sort operation
   private int resultCode;
 
   @Override
   public Control createRequestControl() {
      return null;
   }
    
   @Override
   protected void handleResponse(Object control) {
   }
    
   public String[] getSortKeys() {
      return sortKeys;
   }
    
   public boolean isSorted() {
      return sorted;
   }
    
   public int getResultCode() {
      return resultCode;
   }
}
    

The next step is to provide necessary information to AbstractFallbackRequestAndResponseControlDirContextProcessor for loading the controls. The AbstractFallbackRequestAndResponseControlDirContextProcessor expects two pieces of information from subclasses: the fully qualified class names of the request and response controls to be used, and the fully qualified class names of the controls that should be used as fallback. Listing 7-7 shows the constructor code that does this.

Listing 7-7.

public SortMultipleControlDirContextProcessor(String ... sortKeys) {
    
   if(sortKeys.length == 0) {
      throw new IllegalArgumentException("You must provide " + "atlease one key to sort on");
   }
    
   this.sortKeys = sortKeys;
   this.sorted = false;
   this.resultCode = -1;
   this.defaultRequestControl = "javax.naming.ldap.SortControl";
   this.defaultResponseControl = "javax.naming.ldap.SortResponseControl";
   this.fallbackRequestControl = "com.sun.jndi.ldap.ctl.SortControl";
   this.fallbackResponseControl = "com.sun.jndi.ldap.ctl.SortResponseControl";
    
   loadControlClasses();
}

Notice that you have provided the control classes that come with the JDK as the default controls to be used and the controls that come with the LDAP booster pack as the fallback controls. On the last line of the constructor, you instruct the AbstractFallbackRequestAndResponseControlDirContextProcessor class to load the classes into JVM for usage.

The next step in the process is to provide implementation to the createRequestControl method. Since the superclass AbstractFallbackRequestAndResponseControlDirContextProcessor will take care of the actual creation of the control, all you need to do is to provide the information necessary for creating the control. The following code shows this:

@Override
public Control createRequestControl() {
   return super.createRequestControl(new Class[] {String[].class, boolean.class }, new Object[] { sortKeys, critical });
}

The final step in the implementation is to analyze the response control and retrieve the information regarding the completed operation. Listing 7-8 shows the code involved. Notice that you are using reflection to retrieve the sorted and result code information from the response control.

Listing 7-8.

@Override
protected void handleResponse(Object control) {
 
   Boolean result = (Boolean) invokeMethod("isSorted", responseControlClass, control);
   this.sorted = result;
    
   Integer code = (Integer) invokeMethod("getResultCode", responseControlClass, control);
   this.resultCode = code;
}

Now that you have created a new DirContextProcessor instance that allows you to sort on multiple attributes, let’s take it for a spin. Listing 7-9 shows a sort method that uses the SortMultipleControlDirContextProcessor. The method uses the attributes st and l for sorting the results.

Listing 7-9.

public void sortByLocation() {
    
   String[] locationAttributes = {"st", "l"};
   SortMultipleControlDirContextProcessor smcdcp = new SortMultipleControlDirContextProcessor(locationAttributes);
   SearchControls searchControls = new SearchControls();
   searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
    
   EqualsFilter equalsFilter = new EqualsFilter("objectClass","inetOrgPerson");
 
   @SuppressWarnings("unchecked")
   ParameterizedContextMapper<String> locationMapper = new AbstractParameterizedContextMapper<String>() {
    
      @Override
      protected String doMapFromContext(DirContextOperations context) {
         return context.getStringAttribute("st") + "," + context.getStringAttribute("l");
      }
   };
 
   List<String> results = ldapTemplate.search("", equalsFilter.encode(), searchControls, locationMapper, smcdcp);
   for(String r : results) {
      System.out.println(r);
   }
}

Upon invoking the method, the sorted locations will be displayed on the console as shown:

AK,Abilene
AK,Florence
AK,Sioux Falls
AK,Wilmington
AL,Glendive
AR,Gainesville
AR,Green Bay
AZ,Gainesville
AZ,Moline
AZ,Reno
AZ,Saint Joseph
AZ,Wilmington
CA,Buffalo
CA,Ottumwa
CO,Charlottesville
CO,Lake Charles
CT,Quincy
CT,Youngstown
...............

Paged Search Controls

The paged results control allows LDAP clients to control the rate at which the results of an LDAP search operation are returned. The LDAP clients create a page control with a specified page size and associate it with the search request. Upon receiving the request, the LDAP server will return the results in chunks, with each chunk containing the specified number of results. The paged results control is highly useful when dealing with large directories or building search applications with paging capabilities. This control is described in RFC 2696 (www.ietf.org/rfc/rfc2696.txt).

Figure 7-6 describes the interaction between the LDAP client and server using a page control.

9781430263975_Fig07-06.jpg

Figure 7-6. Page control interaction

image Note   LDAP servers often use the sizeLimit directive to restrict the number of results that are returned for a search operation. If a search produces more results than the specified sizeLimit, a size limit exceeded exception javax.naming.SizeLimitExceededException is thrown. The paging method does not let you pass through this limit.

As a first step, the LDAP client sends the search request along with the page control. Upon receiving the request, the LDAP server executes the search operation and returns the first page of results. Additionally, it sends a cookie that needs to be used to request the next paged results set. This cookie enables the LDAP server to maintain the search state. The client must not make any assumptions about the internal structure of the cookie. When the client makes a request for the next batch of results, it sends the same search request and page control, and the cookie. The server responds with the new result set and a new cookie. When there are no more search results to be returned, the server sends an empty cookie.

Paging using the paged search control is unidirectional and sequential. It is not possible for the client to jump between pages or go back. Now that you know the basics of the paging control, Listing 7-10 shows its implementation using the plain JNDI API.

Listing 7-10.

public void pageAll() {
 
   try {
      LdapContext context = getContext();
      PagedResultsControl prc = new PagedResultsControl(20, Control.CRITICAL);
      context.setRequestControls(new Control[]{prc});
      byte[] cookie = null;
      SearchControls searchControls = new SearchControls();
      searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
      do {
         NamingEnumeration results = context.search("dc=inflinx,dc=com","(objectClass=inetOrgPerson)",searchControls);
         // Iterate over search results
         while(results != null && results.hasMore()) {
           // Display an entry
           SearchResult entry = (SearchResult)results.next();
           System.out.println(entry.getAttributes().get("sn") + " ( " + (entry.getName())+ " )");
         }
         // Examine the paged results control response
         Control[] controls = context.getResponseControls();
         if (controls != null) {
           for(int i = 0; i < controls.length; i++) {
             if(controls[i] instanceof PagedResultsResponseControl) {
               PagedResultsResponseControl prrc =(PagedResultsResponseControl)controls[i];
               int resultCount = prrc.getResultSize();
               cookie = prrc.getCookie();
             }
           }
        }
        // Re-activate paged results
        context.setRequestControls(new Control[]{
        new PagedResultsControl(20, cookie, Control.CRITICAL)});
      } while(cookie != null);
      
      context.close();
   }
   catch(Exception e) {
      e.printStackTrace();
   }
}

In Listing 7-10, you start the implementation by obtaining a context on the LDAP server. Then you create the PagedResultsControl and specify the page size as its constructor parameter. You add the control to the context and performed the search operation. Then you loop through the search results and display the information on the console. As the next step, you examine the response controls to indentify the server sent PagedResultsResponseControl. From that control you extract the cookie and an estimated total number of results for this search. The result count is optional information and the server can simply return zero indicating that unknown count. Finally, you create a new PagedResultsControl with the page size and the cookie as its constructor parameter. This process is repeated until the server sends an empty (null) cookie indicating that there are no more results to be processed.

Spring LDAP abstracts most of the code in Listing 7-10 and makes it easy to deal with page controls using the PagedResultsDirContextProcessor. Listing 7-11 shows the Spring LDAP code.

Listing 7-11.

public void pagedResults() {
 
   PagedResultsCookie cookie = null;
   SearchControls searchControls = new SearchControls();
   searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
   int page = 1;
   do {
      System.out.println("Starting Page: " + page);
      PagedResultsDirContextProcessor processor = new PagedResultsDirContextProcessor(20,cookie);
      EqualsFilter equalsFilter = new EqualsFilter("objectClass","inetOrgPerson");
      List<String> lastNames = ldapTemplate.search("", equalsFilter.encode(), searchControls, new LastNameMapper(), processor);
      for(String l : lastNames) {
         System.out.println(l);
      }
      cookie = processor.getCookie();
      page = page + 1;
   } while(null != cookie.getCookie());
}

In this implementation, you create the PagedResultsDirContextProcessor with the page size and a cookie. Note that you are using the org.springframework.ldap.control.PagedResultsCookie class for abstracting the cookie sent by the server. The cookie value initially starts with a null. Then you perform the search and loop through the results. The cookie sent by the server is extracted from the DirContextProcessor and is used to check for future search requests. You are also using a LastNameMapper class to extract the last name from the results context. Listing 7-12 gives the implementation of the LastNameMapper class.

Listing 7-12.

private class LastNameMapper extends AbstractParameterizedContextMapper<String> {
 
   @Override
   protected String doMapFromContext(DirContextOperations context) {
      return context.getStringAttribute("sn");
   }
}

Summary

In this chapter you learned the basic concepts associated with LDAP controls. You then looked at the sort control, which can be used to perform server-side sorting of the results. You saw how Spring LDAP simplifies the sort control usage significantly. The paging control can be used to page LDAP results, which can be very useful under heavy traffic conditions.

In the next chapter, you will look at using Spring LDAP ODM technology for implementing the data access layer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset