Searching & Comparing Directory Data Traditionally directories excel at serving read requests. This chapter covers the read (search and compare) capabilities that OpenDJ LDAP Java SDK provides. The data used in examples here is available online.
About Searching Searches An LDAP search looks up entries based on the following parameters. A filter that indicates which attribute values to match A base DN that specifies where in the directory information tree to look for matches A scope that defines how far to go under the base DN A list of attributes to fetch for an entry when a match is found For example, imagine you must write an application where users login using their email address and a password. After the user logs in, your application displays the user's full name so it is obvious who is logged in. Your application is supposed to go to the user directory both for authentication, and also to read user profile information. You are told the user directory stores user profile entries under base DN ou=People,dc=example,dc=com, that email addresses are stored on the standard mail attribute, and full names are store on the standard cn attribute. You figure out how to authenticate from the chapter on authentication, in which you learn you need a bind DN and a password to do simple authentication. But how do you find the bind DN given the email? How do you get the full name? The answer to both questions is that you do an LDAP search for the user's entry, which has the DN that you use to bind, and you have the server fetch the cn attribute in the results. Your search uses the following parameters. The filter is (mail=emailAddress), where emailAddress is the email address the user provided. The base DN is the one given to you, ou=People,dc=example,dc=com. For the scope, you figure the user entry is somewhere under the base DN, so you opt to search the whole subtree. The attribute to fetch is cn. The following code excerpt demonstrates how this might be done in a minimal command-line program. // Prompt for mail and password. Console c = System.console(); if (c == null) { System.err.println("No console."); System.exit(1); } String mail = c.readLine("Email address: "); char[] password = c.readPassword("Password: "); // Search using mail address, and then bind with the DN and password. final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port); Connection connection = null; try { connection = factory.getConnection(); // No explicit bind yet so we remain anonymous for now. SearchResultEntry entry = connection.searchSingleEntry(baseDN, SearchScope.WHOLE_SUBTREE, "(mail=" + mail + ")", "cn"); DN bindDN = entry.getName(); connection.bind(bindDN.toString(), password); String cn = entry.getAttribute("cn").firstValueAsString(); System.out.println("Hello, " + cn + "!"); } catch (final ErrorResultException e) { System.err.println("Failed to bind."); System.exit(e.getResult().getResultCode().intValue()); return; } finally { if (connection != null) { connection.close(); } } For a complete example in context, see SearchBind.java, one of the OpenDJ LDAP SDK examples.
Setting Search Base & Scope Searches Base Searches Scope Directory servers organize entries somewhat like a file system. Directory data is often depicted as an upside-down tree. Directory data is often depicted as an upside-down tree. This figure shows three levels, the base DN for the suffix, a couple of organizational units, and three user entries. In the figure shown above, entries are represented by the relevant parts of their DNs. The entry with DN dc=example,dc=com is the base entry for a suffix. Under the base entry, you see two organizational units, one for people, ou=People, the other for groups, ou=Groups. The entries for people include those of Babs Jensen, Kirsten Vaughan, and Sam Carter. When you are searching for a person's entry somewhere under dc=example,dc=com, you can start from dc=example,dc=com, from ou=People,dc=example,dc=com, or if you have enough information to pinpoint the user entry and only want to look up another attribute value for example, then directly from the entry such as cn=Babs Jensen,ou=People,dc=example,dc=com. The DN of the entry where you choose to start the search is the base DN for the search. When searching, you also define the scope. Scope defines what entries the server considers when checking for entries that match your search. For SearchScope.BASE_OBJECT the server considers only the base entry. This is the scope you use if you know the full DN of the object that interests you. For example, if your base DN points to Babs Jensen's entry, cn=Babs Jensen,ou=People,dc=example,dc=com, and you want to read some of Babs's attributes, you would set scope to SearchScope.BASE_OBJECT. For SearchScope.SINGLE_LEVEL the server considers all entries directly below the base entry. You use this scope if for example you want to discover organizational units under dc=example,dc=com, or if you want to find people's entries and you know they are immediately under ou=People,dc=example,dc=com. For SearchScope.SUBORDINATES the server considers all entries below the base entry. This scope can be useful if you know that the base DN for your search is an entry that you do not want to match. For SearchScope.WHOLE_SUBTREE (default) the server considers the base entry and all entries below. In addition to a base DN and scope, a search request also calls for a search filter.
Working With Search Filters Filters Searches Filters When you look someone up in the telephone directory, you use the value of one attribute of a person's entry (last name), to recover the person's directory entry, which has other attributes (phone number, address). LDAP works the same way. In LDAP, search requests identify both the scope of the directory entries to consider (for example, all people or all organizations), and also the entries to retrieve based on some attribute value (for example, surname, mail address, phone number, or something else). The way you express the attribute value(s) to match is by using a search filter. LDAP search filters define what entries actually match your request. For example, the following simple equality filter says, "Match all entries that have a surname attribute (sn) value equivalent to Jensen." (sn=Jensen) When you pass the directory server this filter as part of your search request, the directory server checks the entries in scope for your search to see whether they match.In fact, the directory server probably checks an index first, and might not even accept search requests unless it can use indexes to match your filter rather than checking all entries in scope. If the directory server finds entries that match, it returns those entries as it finds them. The example, (sn=Jensen), shows a string representation of the search filter. The OpenDJ LDAP SDK lets you express your filters as strings, or as Filter objects. In both cases, the SDK translates the strings and objects into the binary representation sent to the server over the network. Equality is just one of the types of comparisons available in LDAP filters. Comparison operators include the following. When taking user input, take care to protect against users providing input that has unintended consequences. OpenDJ SDK offers several Filter methods to help you. First, you can use strongly typed construction methods such as Filter.equality(). String userInput = getUserInput(); Filter filter = Filter.equality("cn", userInput); // Invoking filter.toString() with input of "*" results in a filter // string "(cn=\2A)". You can also let the SDK escape user input by using a template with Filter.format() as in the following example. String template = "(|(cn=%s)(uid=user.%s))"; String[] userInput = getUserInput(); Filter filter = Filter.format(template, userInput[0], userInput[1]); Finally, you can explicitly escape user input with Filter.escapeAssertionValue(). String baseDN = "ou=people,dc=example,dc=com"; String userInput = getUserInput(); // Filter.escapeAssertionValue() transforms user input of "*" to "\2A". SearchRequest request = Requests.newSearchRequest( baseDN, SearchScope.WHOLE_SUBTREE, "(cn=" + Filter.escapeAssertionValue(userInput) + "*)", "cn", "mail");
Sending a Search Request Connections Synchronous Searches As shown in the following excerpt with a synchronous connection, you get a Connection to the directory server from an LDAPConnectionFactory. final LDAPConnectionFactory factory = new LDAPConnectionFactory(host, port); Connection connection = null; try { connection = factory.getConnection(); // Do something with the connection... } catch (Exception e) { // Handle exceptions... } finally { if (connection != null) { connection.close(); } } The Connection gives you search() methods that either take parameters in the style of the ldapsearch command, or that take a SearchRequest object. If you are sure that the search only returns a single entry, you can read the entry with the searchSingleEntry() methods. If you have the distinguished name, you can use readEntry() directly. For a complete example in context, see Search.java, one of the OpenDJ LDAP SDK examples.
Getting Search Results Searches Handling results Depending on the method you use to search, you handle results in different ways. You can get a ConnectionEntryReader, and iterate over the reader to access individual search results. Connection connection = ...; ConnectionEntryReader reader = connection.search("dc=example,dc=com", SearchScope.WHOLE_SUBTREE, "(objectClass=person)"); try { while (reader.hasNext()) { if (reader.isEntry()) { SearchResultEntry entry = reader.readEntry(); // Handle entry... } else { SearchResultReference ref = reader.readReference(); // Handle continuation reference... } } } catch (IOException e) { // Handle exceptions... } finally { reader.close(); } For a complete example in context, see Search.java, one of the OpenDJ LDAP SDK examples. You can pass in a collection of SearchResultEntrys (and optionally a collection of SearchResultReferences) to which the SDK adds the results. For this to work, you need enough memory to hold everything the search returns. You can pass in a SearchResultHandler to manage results. With searchSingleEntry() and readEntry(), you can get a single SearchResultEntry with methods to access the entry content.
Working With Entry Attributes Attributes When you get an entry object, chances are you want to handle attribute values as objects. The OpenDJ LDAP SDK provides the Entry.parseAttribute() method and an AttributeParser with methods for a variety of attribute value types. You can use these methods to get attribute values as objects. // Use Kirsten Vaughan's credentials and her entry. String name = "uid=kvaughan,ou=People,dc=example,dc=com"; char[] password = "bribery".toCharArray(); connection.bind(name, password); // Make sure we have a timestamp to play with. updateEntry(connection, name, "description"); // Read Kirsten's entry. final SearchResultEntry entry = connection.readEntry(name, "cn", "objectClass", "hasSubordinates", "numSubordinates", "isMemberOf", "modifyTimestamp"); // Get the entry DN and some attribute values as objects. DN dn = entry.getName(); Set<String> cn = entry.parseAttribute("cn").asSetOfString(""); Set<AttributeDescription> objectClasses = entry.parseAttribute("objectClass").asSetOfAttributeDescription(); boolean hasChildren = entry.parseAttribute("hasSubordinates").asBoolean(); int numChildren = entry.parseAttribute("numSubordinates").asInteger(0); Set<DN> groups = entry .parseAttribute("isMemberOf") .usingSchema(Schema.getDefaultSchema()).asSetOfDN(); Calendar timestamp = entry .parseAttribute("modifyTimestamp") .asGeneralizedTime().toCalendar(); // Do something with the objects. // ... For a complete example in context, see ParseAttributes.java, one of the OpenDJ LDAP SDK examples.
Working With LDAP URLs LDAP URLs Referrals LDAP URLs express search requests in URL form. In the directory data you can find them used as memberURL attribute values for dynamic groups, for example. The following URL from the configuration for the administrative backend lets the directory server build a dynamic group of administrator entries that are children of cn=Administrators,cn=admin data. ldap:///cn=Administrators,cn=admin data??one?(objectclass=*) The static method LDAPUrl.valueOf() takes an LDAP URL string and returns an LDAPUrl object. You can then use the LDAPUrl.asSearchRequest() method to get the SearchRequest that you pass to one of the search methods for the connection.
Sorting Search Results Searches Handling results Sorting If you want to sort search results in your client application, then make sure you have enough memory in the JVM to hold the results of the search, and use one of the search methods that lets you pass in a collection of SearchResultEntrys. After the collection is populated with the results, you can sort them. If you are on good terms with your directory administrator, you can perhaps use a server-side sort control. The server-side sort request control asks the server to sort the results before returning them, and so is a memory intensive operation on the directory server. You set up the control using ServerSideSortRequestControl.newControl(). You get the control into your search by building a search request to pass to the search method, using SearchRequest.addControl() to attach the control before passing in the request. If your application needs to scroll through search results a page at a time, work with your directory administrator to set up the virtual list view indexes that facilitate scrolling through results.
About Comparing Comparisons You use the LDAP compare operation to make an assertion about an attribute value on an entry. Unlike the search operation, you must know the distinguished name of the entry in advance to request a compare operation. You also specify the attribute type name and the value to compare to the values stored on the entry. Connection has a choice of compare methods, depending on how you set up the operation. Check the ResultCode from CompareResult.getResultCode() for ResultCode.COMPARE_TRUE or ResultCode.COMPARE_FALSE.