<?xml version="1.0" encoding="UTF-8"?>
|
<!--
|
! CCPL HEADER START
|
!
|
! This work is licensed under the Creative Commons
|
! Attribution-NonCommercial-NoDerivs 3.0 Unported License.
|
! To view a copy of this license, visit
|
! http://creativecommons.org/licenses/by-nc-nd/3.0/
|
! or send a letter to Creative Commons, 444 Castro Street,
|
! Suite 900, Mountain View, California, 94041, USA.
|
!
|
! You can also obtain a copy of the license at
|
! trunk/opendj3/legal-notices/CC-BY-NC-ND.txt.
|
! See the License for the specific language governing permissions
|
! and limitations under the License.
|
!
|
! If applicable, add the following below this CCPL HEADER, with the fields
|
! enclosed by brackets "[]" replaced with your own identifying information:
|
! Portions Copyright [yyyy] [name of copyright owner]
|
!
|
! CCPL HEADER END
|
!
|
! Copyright 2011-2013 ForgeRock AS
|
!
|
-->
|
<chapter xml:id='chap-reading'
|
xmlns='http://docbook.org/ns/docbook' version='5.0' xml:lang='en'
|
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
|
xsi:schemaLocation='http://docbook.org/ns/docbook http://docbook.org/xml/5.0/xsd/docbook.xsd'
|
xmlns:xlink='http://www.w3.org/1999/xlink'
|
xmlns:xinclude='http://www.w3.org/2001/XInclude'>
|
<title>Searching & Comparing Directory Data</title>
|
|
<para>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 <link xlink:show="new"
|
xlink:href="http://opendj.forgerock.org/Example.ldif">available
|
online</link>.</para>
|
|
<section xml:id="about-searching">
|
<title>About Searching</title>
|
<indexterm>
|
<primary>Searches</primary>
|
</indexterm>
|
|
<itemizedlist>
|
<para>An LDAP search looks up entries based on the following
|
parameters.</para>
|
<listitem>
|
<para>A <firstterm>filter</firstterm> that indicates which attribute values
|
to match</para>
|
</listitem>
|
<listitem>
|
<para>A <firstterm>base DN</firstterm> that specifies where in the
|
directory information tree to look for matches</para>
|
</listitem>
|
<listitem>
|
<para>A <firstterm>scope</firstterm> that defines how far to go under
|
the base DN</para>
|
</listitem>
|
<listitem>
|
<para>A list of attributes to fetch for an entry when a match is
|
found</para>
|
</listitem>
|
</itemizedlist>
|
|
<para>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
|
<literal>ou=People,dc=example,dc=com</literal>, that email addresses are
|
stored on the standard <literal>mail</literal> attribute, and full names are
|
store on the standard <literal>cn</literal> attribute.</para>
|
|
<para>You figure out how to authenticate from the chapter on <link
|
xlink:href="dev-guide#chap-authenticating"
|
xlink:role="http://docbook.org/xlink/role/olink">authentication</link>,
|
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?</para>
|
|
<para>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 <literal>cn</literal> attribute in the results. Your search uses
|
the following parameters.</para>
|
<itemizedlist>
|
<listitem>
|
<para>The filter is
|
<literal>(mail=<replaceable>emailAddress</replaceable>)</literal>, where
|
<replaceable>emailAddress</replaceable> is the email address the user
|
provided.</para>
|
</listitem>
|
<listitem>
|
<para>The base DN is the one given to you,
|
<literal>ou=People,dc=example,dc=com</literal>.</para>
|
</listitem>
|
<listitem>
|
<para>For the scope, you figure the user entry is somewhere under the base
|
DN, so you opt to search the whole subtree.</para>
|
</listitem>
|
<listitem>
|
<para>The attribute to fetch is <literal>cn</literal>.</para>
|
</listitem>
|
</itemizedlist>
|
|
<para>The following code excerpt demonstrates how this might be done in a
|
minimal command-line program.</para>
|
|
<programlisting language="java">// 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();
|
}
|
}</programlisting>
|
|
<para>For a complete example in context, see <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/xref/org/forgerock/opendj/examples/SearchBind.html"
|
xlink:show="new">SearchBind.java</link>, one of the <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/"
|
xlink:show="new">OpenDJ LDAP SDK examples</link>.</para>
|
</section>
|
|
<section xml:id="basedn-and-scope">
|
<title>Setting Search Base & Scope</title>
|
<indexterm>
|
<primary>Searches</primary>
|
<secondary>Base</secondary>
|
</indexterm>
|
<indexterm>
|
<primary>Searches</primary>
|
<secondary>Scope</secondary>
|
</indexterm>
|
|
<para>Directory servers organize entries somewhat like a file system.
|
Directory data is often depicted as an upside-down tree.</para>
|
|
<mediaobject xml:id="figure-ldap-tree">
|
<alt>Directory data is often depicted as an upside-down tree.</alt>
|
<imageobject>
|
<imagedata fileref="images/ldap-tree.png" format="PNG" />
|
</imageobject>
|
<textobject>
|
<para>This figure shows three levels, the base DN for the suffix, a couple
|
of organizational units, and three user entries.</para>
|
</textobject>
|
</mediaobject>
|
|
<para>In the figure shown above, entries are represented by the relevant
|
parts of their DNs. The entry with DN <literal>dc=example,dc=com</literal>
|
is the base entry for a suffix. Under the base entry, you see two
|
organizational units, one for people, <literal>ou=People</literal>, the other
|
for groups, <literal>ou=Groups</literal>. The entries for people include
|
those of Babs Jensen, Kirsten Vaughan, and Sam Carter.</para>
|
|
<para>When you are searching for a person's entry somewhere under
|
<literal>dc=example,dc=com</literal>, you can start from
|
<literal>dc=example,dc=com</literal>, from
|
<literal>ou=People,dc=example,dc=com</literal>, 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
|
<literal>cn=Babs Jensen,ou=People,dc=example,dc=com</literal>. The DN of
|
the entry where you choose to start the search is the base DN for the
|
search.</para>
|
|
<itemizedlist>
|
<para>When searching, you also define the scope. Scope defines what entries
|
the server considers when checking for entries that match your search.</para>
|
<listitem>
|
<para>For <literal>SearchScope.BASE_OBJECT</literal> the server considers
|
only the base entry.</para>
|
<para>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, <literal>cn=Babs Jensen,ou=People,dc=example,dc=com</literal>, and
|
you want to read some of Babs's attributes, you would set scope to
|
<literal>SearchScope.BASE_OBJECT</literal>.</para>
|
</listitem>
|
<listitem>
|
<para>For <literal>SearchScope.SINGLE_LEVEL</literal> the server considers
|
all entries directly below the base entry.</para>
|
<para>You use this scope if for example you want to discover organizational
|
units under <literal>dc=example,dc=com</literal>, or if you want to find
|
people's entries and you know they are immediately under
|
<literal>ou=People,dc=example,dc=com</literal>.</para>
|
</listitem>
|
<listitem>
|
<para>For <literal>SearchScope.SUBORDINATES</literal> the server considers
|
all entries below the base entry.</para>
|
<para>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.</para>
|
</listitem>
|
<listitem>
|
<para>For <literal>SearchScope.WHOLE_SUBTREE</literal> (default) the server
|
considers the base entry and all entries below.</para>
|
</listitem>
|
</itemizedlist>
|
|
<para>In addition to a base DN and scope, a search request also calls for a
|
search filter.</para>
|
</section>
|
|
<section xml:id="about-filters">
|
<title>Working With Search Filters</title>
|
<indexterm>
|
<primary>Filters</primary>
|
</indexterm>
|
<indexterm>
|
<primary>Searches</primary>
|
<secondary>Filters</secondary>
|
</indexterm>
|
|
<para>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.</para>
|
|
<para>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."</para>
|
|
<literallayout class="monospaced">(sn=Jensen)</literallayout>
|
|
<para>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.<footnote><para>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.</para></footnote> If the directory server finds entries that match,
|
it returns those entries as it finds them.</para>
|
|
<para>The example, <literal>(sn=Jensen)</literal>, shows a string
|
representation of the search filter. The OpenDJ LDAP SDK lets you express
|
your filters as strings, or as <literal>Filter</literal> objects. In both
|
cases, the SDK translates the strings and objects into the binary
|
representation sent to the server over the network.</para>
|
|
<para>Equality is just one of the types of comparisons available in LDAP
|
filters. Comparison operators include the following.</para>
|
|
<xinclude:include href="../shared/table-filter-operators.xml" />
|
|
<para>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 <literal>Filter.equality()</literal>.</para>
|
|
<programlisting language="java">String userInput = getUserInput();
|
Filter filter = Filter.equality("cn", userInput);
|
|
// Invoking filter.toString() with input of "*" results in a filter
|
// string "(cn=\2A)".</programlisting>
|
|
<para>You can also let the SDK escape user input by using a template with
|
<literal>Filter.format()</literal> as in the following example.</para>
|
|
<programlisting language="java">String template = "(|(cn=%s)(uid=user.%s))";
|
String[] userInput = getUserInput();
|
Filter filter = Filter.format(template, userInput[0], userInput[1]);</programlisting>
|
|
<para>Finally, you can explicitly escape user input with
|
<literal>Filter.escapeAssertionValue()</literal>.</para>
|
|
<programlisting language="java">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");</programlisting>
|
</section>
|
|
<section xml:id="send-search-request">
|
<title>Sending a Search Request</title>
|
<indexterm>
|
<primary>Connections</primary>
|
<secondary>Synchronous</secondary>
|
</indexterm>
|
<indexterm>
|
<primary>Searches</primary>
|
</indexterm>
|
|
<para>As shown in the following excerpt with a synchronous connection, you
|
get a <literal>Connection</literal> to the directory server from an
|
<literal>LDAPConnectionFactory</literal>.</para>
|
|
<programlisting language="java"
|
>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();
|
}
|
}</programlisting>
|
|
<para>The <literal>Connection</literal> gives you <literal>search()</literal>
|
methods that either take parameters in the style of the
|
<command>ldapsearch</command> command, or that take a
|
<literal>SearchRequest</literal> object. If you are sure that the search only
|
returns a single entry, you can read the entry with the
|
<literal>searchSingleEntry()</literal> methods. If you have the distinguished
|
name, you can use <literal>readEntry()</literal> directly.</para>
|
|
<para>For a complete example in context, see <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/xref/org/forgerock/opendj/examples/Search.html"
|
xlink:show="new">Search.java</link>, one of the <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/"
|
xlink:show="new">OpenDJ LDAP SDK examples</link>.</para>
|
</section>
|
|
<section xml:id="get-search-results">
|
<title>Getting Search Results</title>
|
<indexterm>
|
<primary>Searches</primary>
|
<secondary>Handling results</secondary>
|
</indexterm>
|
|
<para>Depending on the method you use to search, you handle results in
|
different ways.</para>
|
|
<itemizedlist>
|
<listitem>
|
<para>You can get a <literal>ConnectionEntryReader</literal>, and iterate
|
over the reader to access individual search results.</para>
|
|
<programlisting language="java">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();
|
}</programlisting>
|
|
<para>For a complete example in context, see <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/xref/org/forgerock/opendj/examples/Search.html"
|
xlink:show="new">Search.java</link>, one of the <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/"
|
xlink:show="new">OpenDJ LDAP SDK examples</link>.</para>
|
</listitem>
|
|
<listitem>
|
<para>You can pass in a collection of <literal>SearchResultEntry</literal>s
|
(and optionally a collection of <literal>SearchResultReference</literal>s)
|
to which the SDK adds the results. For this to work, you need enough
|
memory to hold everything the search returns.</para>
|
</listitem>
|
|
<listitem>
|
<para>You can pass in a <literal>SearchResultHandler</literal> to manage
|
results.</para>
|
</listitem>
|
|
<listitem>
|
<para>With <literal>searchSingleEntry()</literal> and
|
<literal>readEntry()</literal>, you can get a single
|
<literal>SearchResultEntry</literal> with methods to access the entry
|
content.</para>
|
</listitem>
|
</itemizedlist>
|
</section>
|
|
<section xml:id="handle-entry-attributes">
|
<title>Working With Entry Attributes</title>
|
<indexterm>
|
<primary>Attributes</primary>
|
</indexterm>
|
|
<para>When you get an entry object, chances are you want to handle attribute
|
values as objects. The OpenDJ LDAP SDK provides the
|
<literal>Entry.parseAttribute()</literal> method and an
|
<literal>AttributeParser</literal> with methods for a variety of attribute
|
value types. You can use these methods to get attribute values as
|
objects.</para>
|
|
<programlisting language="java">
|
// 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.
|
// ...
|
</programlisting>
|
|
<para>For a complete example in context, see <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/xref/org/forgerock/opendj/examples/ParseAttributes.html"
|
xlink:show="new">ParseAttributes.java</link>, one of the <link
|
xlink:href="http://opendj.forgerock.org/opendj-ldap-sdk-examples/"
|
xlink:show="new">OpenDJ LDAP SDK examples</link>.</para>
|
</section>
|
|
<section xml:id="handle-ldap-urls">
|
<title>Working With LDAP URLs</title>
|
<indexterm>
|
<primary>LDAP</primary>
|
<secondary>URLs</secondary>
|
</indexterm>
|
<indexterm>
|
<primary>Referrals</primary>
|
</indexterm>
|
|
<para>LDAP URLs express search requests in URL form. In the directory data
|
you can find them used as <literal>memberURL</literal>
|
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
|
<literal>cn=Administrators,cn=admin data</literal>.</para>
|
|
<literallayout class="monospaced"
|
>ldap:///cn=Administrators,cn=admin data??one?(objectclass=*)</literallayout>
|
|
<para>The static method <literal>LDAPUrl.valueOf()</literal> takes an LDAP
|
URL string and returns an <literal>LDAPUrl</literal> object. You can then use
|
the <literal>LDAPUrl.asSearchRequest()</literal> method to get the
|
<literal>SearchRequest</literal> that you pass to one of the search methods
|
for the connection.</para>
|
</section>
|
|
<section xml:id="sort-search-results">
|
<title>Sorting Search Results</title>
|
<indexterm>
|
<primary>Searches</primary>
|
<secondary>Handling results</secondary>
|
</indexterm>
|
<indexterm>
|
<primary>Sorting</primary>
|
</indexterm>
|
|
<para>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
|
<literal>SearchResultEntry</literal>s. After the collection is populated with
|
the results, you can sort them.</para>
|
|
<para>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 <literal>ServerSideSortRequestControl.newControl()</literal>. You get
|
the control into your search by building a search request to pass to the
|
search method, using <literal>SearchRequest.addControl()</literal> to attach
|
the control before passing in the request.</para>
|
|
<para>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.</para>
|
</section>
|
|
<section xml:id="about-comparisons">
|
<title>About Comparing</title>
|
<indexterm>
|
<primary>Comparisons</primary>
|
</indexterm>
|
|
<para>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.</para>
|
|
<para><literal>Connection</literal> has a choice of compare methods,
|
depending on how you set up the operation.</para>
|
|
<para>Check the <literal>ResultCode</literal> from
|
<literal>CompareResult.getResultCode()</literal> for
|
<literal>ResultCode.COMPARE_TRUE</literal> or
|
<literal>ResultCode.COMPARE_FALSE</literal>.</para>
|
</section>
|
</chapter>
|