Ease of Use
The UnboundID LDAP SDK for Java was specifically designed to be very user-friendly.
We have a great deal of experience working with real-world directory environments
and writing directory-enabled applications, and as a result we are well aware of
the challenges and frustrations that may be encountered when working with other
LDAP SDKs.
The primary reason that JNDI is commonly-used for directory-enabled applications is
that it is part of the core Java platform. In fact, that is perhaps the only
reason for its popularity, since the API itself is absolutely horrible. It is very
abstract (in fact, it is intended for use in communicating with a number of
different types of systems, so it really isn't an LDAP API as much as an API that
happens to provide LDAP support), which means that performing simple tasks often
require significantly more code than should be required, and that it uses
terminology that can be extremely confusing (e.g., in JNDI, the "bind" method is
used to add an entry to the directory rather than performing an LDAP bind
operation). Further, the abstract nature of the API may make some tasks either
difficult (e.g., performing an LDAP compare operation) or impossible (e.g.,
retrieving the result code from an operation). Finally, JNDI provides virtually no
support for anything but the most basic functionality. It provides no facilities
for interacting with data in LDIF form, or for performing ASN.1 encoding/decoding
needed to support development of custom controls and extensions.
The Netscape Directory SDK for Java is LDAP-centric and therefore much more
user-friendly than JNDI. It doesn't suffer from the terminology conflicts that
plagues JNDI, and it does offer support for additional functionality like parsing
the server schema, LDIF processing, ASN.1 processing, and base64 processing. The
primary concerns related to ease of use of the Netscape Directory SDK for Java
aren't really issues of tasks being difficult to perform but rather that many
common tasks are not as convenient as they could be.
The UnboundID LDAP SDK for Java is rather similar to the Netscape Directory SDK
for Java, and developers familiar with the Netscape SDK should find it easy to
switch to the UnboundID offering. However, many kinds of tasks can be performed
in the UnboundID LDAP SDK for Java more conveniently and with less effort.
Perhaps the best way to illustrate the relative ease of use of these APIs is by
providing a few examples. The rest of this page will demonstrate the code required
to perform common tasks in JNDI, the Netscape Directory SDK for Java, and the
UnboundID LDAP SDK for Java. For the sake of simplicity, exception handling has been
omitted from all examples.
Establishing a Connection
This section will demonstrate the process for establishing a simple LDAPv3
connection (with no SSL or other security) and performing a simple bind operation.
JNDI
In JNDI, when you wish to establish a connection to an LDAP directory server, you
should create an InitialLdapContext object. This can be quite a daunting
task, because the only way that you have to specify the target server, whether or
not to use SSL or some other security mechanism, whether or not to authenticate,
etc. is by providing a set of properties. There aren't any convenience methods to
help you figure out what those properties might be (although there are constants in
some cases), so you have to look through the documentation to try to find them, and
in fact some of these properties and/or values use "com.sun.*" prefixes, so it
isn't even clear whether they are safe to use in code that might run on Java
implementations provided by vendors other than Sun.
Here is an example of the work required to do this in JNDI:
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://server.example.com:389/");
env.put(Context.SECURITY_PRINCIPAL,
"uid=test.user,ou=People,dc=example,dc=com");
env.put(Context.SECURITY_CREDENTIALS, "password");
env.put("java.naming.ldap.version", "3");
LdapContext connection = new InitialLdapContext(env, null);
Netscape Directory SDK for Java
The Netscape Directory SDK for Java is significantly simpler to use than JNDI
because there is no need to worry about figuring out what properties need to be
set. The following example demonstrates the work required to create an
authenticated connection using the Netscape Directory SDK for Java:
LDAPConnection connection = new LDAPConnection();
connection.connect(3, "server.example.com", 389,
"uid=test.user,ou=People,dc=example,dc=com", "password");
Note the inclusion of the "3" as the first argument to the connect method.
This is very important (although unfortunately very easy to overlook, since there
is also a version of the connect method without the version but with all
of the other arguments) because by default the Netscape Directory SDK for Java
uses LDAPv2 and you must override that if you want to use LDAPv3. Otherwise, you
may find strange behavior in your application (e.g., not getting response controls
when you expect them, or not being able to use extended operations, since LDAPv2
doesn't support them).
UnboundID LDAP SDK for Java
The process for establishing a connection using simple authentication in the
UnboundID LDAP SDK for Java is similar to the process for performing the same
operation in the Netscape Directory SDK for Java, but it also provides the
ability to establish the connection and perform simple authentication in the
constructor so it is not necessary to perform as two separate steps (although you
can separate them if you wish to do so, e.g., to be able to get response controls
from the bind operation). Further, there is no need to explicitly request LDAPv3,
since the UnboundID LDAP SDK for Java uses LDAPv3 by default.
LDAPConnection connection = new LDAPConnection("server.example.com", 389,
"uid=test.user,ou=People,dc=example,dc=com", "password");
Performing a Search Operation
This section will demonstrate the process for performing a search to find a user
entry and then retrieve that user's e-mail address from the mail
attribute. In each of these cases, it will be assumed that only one entry will be
returned, so we don't need to worry about the possibility of multiple entries
matching the search criteria.
JNDI
The JNDI "search" methods may be used to perform searches in the
directory, and the simplest of these in most cases is the variant that takes string
representations of the base DN and filter and a SearchControls object.
This is another case where JNDI suffers from a poor choice of terminology because
"controls" in this case does not have anything at all to do with LDAP controls but
rather with a set of miscellaneous options used to perform the search (e.g., the
search scope, attributes to return, size and time limits, etc.).
Something else that directory developers may find confusing is that the methods
used to get attribute values have a generic return type of Object. It is
not clear from the JNDI specification whether these objects will always be strings
or whether they may be other types of objects. Further, it is not necessarily
clear if it is possible to obtain the values in any other form (e.g., the raw bytes
that make up the value).
The following code illustrates the process for performing the desired search
processing in JNDI:
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
searchControls.setReturningAttributes(new String[] { "mail" });
String mail = null;
NamingEnumeration<SearchResult> results =
connection.search("dc=example,dc=com", "(uid=john.doe)", searchControls);
while (results.hasMore())
{
SearchResult result = results.next();
Attributes attributes = result.getAttributes();
Attribute attribute = attributes.get("mail");
if (attribute != null)
{
Object o = attribute.get();
if (o != null)
{
mail = String.valueOf(o);
}
}
}
Netscape Directory SDK for Java
Performing this search using the Netscape Directory SDK requires slightly less
code than the JNDI version, and again is significantly less confusing. The
Netscape Directory SDK provides the ability to get attribute values as either
strings or byte arrays. The process for achieving the desired result, however,
is still not as convenient as it could be, and a significant problem that may
arise is that the LDAPSearchResults.next() method may throw an exception
even if the search is not yet complete (e.g., if the server returns a search
result reference while there are still more entries to be returned, and the SDK
has not been configured to follow referrals, which it is not by default), so just
because an exception is thrown doesn't mean that search processing is over.
The following code illustrates the process for performing the desired search
processing using the Netscape Directory SDK for Java. Note that for simplicity,
this code does not attempt to address the above concern over throwing an exception
when there are still more results to return.
LDAPSearchResults searchResults =
connection.search("dc=example,dc=com", LDAPConnection.SCOPE_SUB,
"(uid=john.doe)", new String[] { "mail" }, false);
String mail = null;
while (searchResults.hasMoreElements())
{
LDAPEntry entry = searchResults.next();
LDAPAttribute attribute = entry.getAttribute("mail");
if (attribute != null)
{
String[] values = attribute.getStringValueArray();
if ((values != null) && (values.length > 0))
{
mail = values[0];
}
}
}
UnboundID LDAP SDK for Java
The following code may be used to perform the same task in the UnboundID LDAP SDK
for Java:
SearchResult searchResults = connection.search("dc=example,dc=com",
SearchScope.SUB, "(uid=john.doe)", "mail");
String mail = null;
if (searchResults.getEntryCount() > 0)
{
SearchResultEntry entry = searchResults.getSearchEntries().get(0);
mail = entry.getAttributeValue("mail");
}
In this case, you can see that the code is much more streamlined and significantly
fewer steps are required to achieve the desired result. You can also see a number
of other benefits that the UnboundID LDAP SDK for Java provides:
-
In the above search request, the set of requested attributes to return can be
specified using varargs (i.e., "String..." rather than
"String[]") so it is not necessary to construct an array to hold the set
of requested attributes (although you can still do so if you want to, since Java
will allow you to provide a single String[] object instead of individual
String objects).
-
The UnboundID LDAP SDK for Java counts the number of matching entries for you, so
you don't have to do that for yourself.
-
The UnboundID LDAP SDK for Java provides the ability to capture all search result
entries and references and include them as Lists in the
SearchResult object. For cases in which the expected set of matching
entries is small, this can be very convenient.
-
The UnboundID LDAP SDK for Java makes matching entries available as
SearchResultEntry objects rather than as plain Entry objects.
SearchResultEntry objects are a subclass of Entry that may also
include controls. This makes it much more convenient (and threadsafe) to access
the set of controls included with each entry.
-
The Entry class provides methods for getting direct access to attribute
values rather than first requiring you to retrieve the specified attribute,
checking to ensure that it isn't null, and then getting the associated value.
It also provides the ability to retrieve a single value or an array of values,
unlike the Netscape Directory SDK for Java which always requires that you treat
the values as arrays.
Performing an Add Operation
This section will demonstrate the process for adding a simple entry to the server.
In this case, the entry added will have the following LDIF representation:
dn: dc=example,dc=com
objectClass: top
objectClass: domain
dc: example
JNDI
As noted above, JNDI uses the poorly-named "bind" method to add an entry to the
server (and "unbind" to delete an entry). The basic reasoning for this is that it
is trying to associate the provided set of data (the attributes) with a name (the
DN), but this can be very confusing and frustrating for developers familiar with
LDAP directories but not very familiar with JNDI because obviously the terms "bind"
and "unbind" have very different meanings in LDAP. Another nit with JNDI is that
Attributes and Attribute are interfaces rather than classes, so
it is necessary to create BasicAttributes and BasicAttribute
objects instead, and it appears that there is no way to create an attribute with
multiple values so it is first necessary to create a BasicAttribute with a
single value and then add additional values to it.
The code to use to add an entry in JNDI is as follows:
Attributes attributes = new BasicAttributes();
Attribute objectClassAttribute = new BasicAttribute("objectClass", "top");
objectClassAttribute.add("domain");
attributes.put(objectClassAttribute);
attributes.put("dc", "example");
connection.bind("dc=example,dc=com", null, attributes);
Netscape Directory SDK for Java
The Netscape Directory SDK for Java uses the more appropriately-named add
method to add entries to a directory server. LDAPAttribute objects to
include in the entries can be directly instantiated, and they can be created with
multiple values. However, it is still a little frustrating that it is necessary
to create an LDAPEntry object in order to perform an add, and that when
creating an entry it is necessary to use an LDAPAttributeSet instead of
being able to use a more generic array or collection of LDAPAttribute
objects.
The code used to perform the add operation in the Netscape Directory SDK for Java
is as follows:
LDAPAttributeSet attributeSet = new LDAPAttributeSet();
attributeSet.add(
new LDAPAttribute("objectClass", new String[] { "top", "domain" }));
attributeSet.add(new LDAPAttribute("dc", "example"));
LDAPEntry entry = new LDAPEntry("dc=example,dc=com", attributeSet);
connection.add(entry);
UnboundID LDAP SDK for Java
The UnboundID LDAP SDK for Java also uses the "add" method to add entries.
However, as you can see this is another case in which the process is significantly
more streamlined than either JNDI or the Netscape SDK:
LDAPResult addResult = connection.add("dc=example,dc=com",
new Attribute("objectClass", "top", "domain"),
new Attribute("dc", "example"));
It isn't necessary to create an entry in this case, since one of the available
add methods supports providing a DN and a variable-length set of
attributes using varargs. Further, the fact that the set of attribute values can
be specified using varargs means that you don't have to explicitly create an array
in order to create a multivalued attribute.
Note, however, that the UnboundID LDAP SDK for Java has another trick up its sleeve
in that it supports adding an entry using its LDIF representation. If you know
exactly what the entry you want to create looks like, then you can use the
following code to add it:
LDAPResult addResult = connection.add(
"dn: dc=example,dc=com",
"objectClass: top",
"objectClass: domain",
"dc: example");
The modify operation also provides the ability to modify existing entries
using the LDIF representation, like:
LDAPResult modifyResult = connection.modify(
"dn: dc=example,dc=com",
"changetype: modify",
"replace: description",
"description: Example");
|