In the first half of this article, I introduced you to the conceptual basis of storing Java objects in ApacheDS. I explained the core architecture of ApacheDS and discussed how it implements directory services and pluggable protocol support. I introduced LDAP concepts and terminology. I explained how ApacheDS implements the LDAP protocol and introduced the various components used to store and manipulate objects in ApacheDS. Finally, I discussed the basics of Java object serialization and RMI, which you must understand before attempting to store and retrieve Java objects in ApacheDS. I also introduced an example application -- a data management system for a manufacturing company -- and used it to demonstrate some of the concepts discussed.
In this second half of the article, I focus almost entirely on examples, nine of them in all. The examples are based on the data management system introduced in Part 1 and serve to teach you how to store, search, retrieve, and update Java objects in Apache DS.
If you have not already done so, be sure to download and install ApacheDS and JXplorer before starting. You can download the complete article source at any time.
Application 1. Store Java objects
I'll start with several applications that demonstrate how to store Java objects in ApacheDS. For this purpose, you need to use the Java Naming and Directory Interface (JNDI), which provides interfaces and methods to work with objects and attributes in a directory. Refer to Storing Java objects in Apache Directory Server, Part 1 for a discussion of how ApacheDS exposes its directory services using the JNDI interface.
JNDI is not an LDAP-specific interface; you can have a JNDI implementation for any type of directory service. If you want to implement your own directory service and expose its functionality using JNDI, you implement JNDI interfaces for your directory service. Note also that Java 2 Standard Edition (J2SE) comes with a client-side JNDI implementation for LDAP, which you can use to talk to ApacheDS. I use this client-side implementation throughout my discussion.
Listing 1 is a simple
application named StoreAlicePreferences.
I'll use this application to show you how to store the user
Alice's preferences as a Java object in ApacheDS.
Listing 1. StoreAlicePreferences
public class StoreAlicePreferences {
public StoreAlicePreferences ()
{
try {
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//------------------------------------------
//Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//------------------------------------------
//Step3: Instantiate a Java object
//------------------------------------------
MessagingPreferences preferences = new MessagingPreferences();
//------------------------------------------
//Step4: Store the Java object in ApacheDS
//------------------------------------------
String bindContext = "cn=preferences,uid=alice,ou=users";
ctx.bind( bindContext, preferences);
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
StoreAlicePreferences storeAlicePref = new StoreAlicePreferences();
}
}
|
As you can see from my comments in Listing 1, four steps are involved in storing a Java object (that is, Alice's preferences) in ApacheDS. The sections that follow discuss each step in detail.
Step 1. Set up JNDI properties for ApacheDS
The first step in Listing 1 is to read an ApacheDS properties file
into a Properties object. This means you must
first write your JNDI properties into a separate properties file, as shown in
Listing 2:
Listing 2. The ApacheDS.properties file
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory java.naming.provider.url=ldap://localhost:389/ou=system java.naming.security.authentication=simple java.naming.security.principal=uid=admin,ou=system |
You need a separate properties file because applications using client-side
JNDI implementations are supposed to work independent of any particular
JNDI implementation. A given Java application (for example
StoreAlicePreferences) should be able to work
with ApacheDS or any other directory service. The directory service
doesn't even need to use LDAP.
The value of the properties file becomes clear when you consider a simple scenario. Suppose you've developed a Java application that uses a client-side JNDI implementation for LDAP to talk to ApacheDS. Later, you purchase another directory server that does not use LDAP but some other protocol.
In this case, you need a new client-side JNDI implementation capable of working with your new directory server. Your Java application will continue to work as such without any recoding. You just update your properties file to reflect the properties of your new directory server.
While there is no programmatic problem with hard-coding the properties within your application, doing so makes your application dependent on the particular implementation you've hard-coded it for. This somewhat defeats the purpose of using JNDI, which is to remain independent of a particular implementation.
Details of the properties file
Now look at the ApacheDS.properties file shown in Listing 2. A properties file consists of a number of name-value pairs, where each name represents a property.
These properties are used while instantiating an object that
exposes a JNDI interface named Context. Context is actually the most important interface in
JNDI. This interface defines methods for working with named contexts.
(I introduced the concept of named contexts along with the example
application in the first half of this article.)
For example, an important method defined in the Context interface is
bind(), which binds a Java object to a named
context. Binding an object to a named context means you are storing your
object in the directory at a particular context with the particular
name. I'll show you how to use the bind() method
in just a moment. First, let's have a look at the name-value pairs in the properties file.
The first name-value pair in Listing 2
is
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory,
which sets the java.naming.factory.initial
JNDI property. The java.naming.factory.initial property specifies the
name of the object factory that you are using as part of your JNDI
client-side implementation. This object factory creates the Context object, which you use to work with
named contexts in ApacheDS.
Because you are using the LDAP-based implementation of JNDI, you specify com.sun.jndi.ldap.LdapCtxFactory as
the value of this property. As you can guess, the
com.sun.jndi.ldap.LdapCtxFactory class constructs the Context object able to communicate with
ApacheDS according to the LDAP protocol.
The second name-value pair in Listing 2 is java.naming.provider.url=ldap://localhost:389/ou=system. The java.naming.provider.url property specifies the URL of the complete directory context in which you want to work.
The complete directory context consists of two components: the URL
where ApacheDS is listening and the named context within ApacheDS in
which you want to work. The string ldap://localhost:389/ specifies the URL where
ApacheDS is listening. The remaining part of the string (ou=system) specifies the named context where you
will work.
The third name-value pair is
java.naming.security.authentication=simple. This property
specifies the strength of security that your ApacheDS employs for user
authentication. This property can have one of the three values: none, simple, or strong:
- If you select "none," ApacheDS does not use authentication and anyone can log in without specifying a password.
- If you select "simple," ApacheDS employs simple password-based authentication, meaning the password travels in plaintext form over the network.
- If you select "strong," a hashed value of the user's password (instead of the actual password in plaintext form) travels to ApacheDS for authentication.
The current version of ApacheDS doesn't support the "strong" level of authentication.
The fourth name-value pair in Listing 2 is
java.naming.security.principal=uid=admin,ou=system. This
property specifies the DN of the user who is trying to log on to ApacheDS. (I am using the DN of ApacheDS administrator (uid=admin,ou=system) as the value of this property.)
You have seen the four name-value pairs in the ApacheDS.properties file of Listing 2. Now revisit Step 1 in Listing 1 in which you read the properties file into a Properties object. Shortly, you will use this Properties object while working with JNDI.
You also need to include the user's password in the Properties
object. A real-world application would typically not keep the user's
password in a configuration file, however; it would receive the password from
the user through its GUI. In Listing 1, I set the password as the value of a property named
java.naming.security.credentials. This property actually
takes a credential to prove the user's identity. There can be several types of credentials (for example, a password or a Kerberos ticket); for the purposes of this article, I'm using password-based authentication.
All properties have been set and the Properties object is ready to be used.
Step 2. Fetch a DirContext object
Next, you instantiate a class named InitialDirContext. This class is part of JNDI and
exposes an interface called DirContext. The
InitialDirContext constructor takes the Properties object discussed above.
The InitialDirContext object is capable
of performing all the directory operations that you could want to perform
on ApacheDS, including storing a new object, searching for already
stored objects, adding attributes to an existing object, and more.
The DirContext interface
extends the Context interface. The Context interface represents a named context, and
the DirContext interface provides the
functionality related to adding, editing, and managing attributes
associated with named contexts.
Put simply, the Context interface provides naming
functionality, and the DirContext interface
extends that naming functionality by adding the support for attributes.
The naming and attributes functionality together make up a
directory service.
You might say that the InitialDirContext object is a wrapper
for the DirContext object instantiated by a
factory object. In the case of this example, the InitialDirContext
constructor uses the context factory object specified by the first
property in Listing 2 (that is, com.sun.jndi.ldap.LdapCtxFactory). The factory
object instantiates an object that exposes the DirContext object, and the InitialDirContext object uses this DirContext object to perform directory operations
as required by a client application.
The main advantage of the ApacheDS directory service, so defined, is to make the client application independent of any particular implementation. A client application specifies a factory object in the configuration file, and the
InitialDirContext object uses the factory
object to instantiate a DirContext object,
which contains all the logic to handle communications with the remote
directory service.
For example, Listing 1 employs a com.sun.jndi.ldap.LdapCtxFactory factory object
from Sun Microsystems. This factory object creates a DirContext object capable of authoring LDAP
requests that ApacheDS can understand.
Later, if you want to run the StoreAlicePreferences application (from Listing 1) with some non-LDAP directory service, you
can simply exchange the name of the factory object in Listing 2 with your new factory object, according to the business logic of your non-LDAP service. The StoreAlicePreferences application then starts working with your non-LDAP service.
Step 3. Instantiate a Java object
Next, you instantiate a class named MessagingPreferences, shown in Listing 3, that
represents Alice's messaging preferences. (Recall my
discussion of messaging preferences from Part 1.)
Listing 3. The MessagingPreferences class
public class MessagingPreferences extends
Preferences implements java.io.Serializable {
static final long serialVersionUID = -1240113639782150930L;
//Methods of the MessagingPreferences class
}
|
At this time, you can also call methods of the MessagingPreferences class to set Alice's
preferences.
In Listing 3, the MessagingPreferences class implements the Serializable interface (which I introduced in the
"Serializing a Java object" section
of Part 1), and something called the serialVersionUID, which I will discuss briefly before moving on.
It is recommended that all serializable
classes contain a private static data member named serialVersionUID, of type long. You don't need
to use this data member anywhere in your serializable classes. The Java
runtime uses this data member during serialization and
deserialization.
The Java object serialization specification (see Resources) specifies a
complex algorithm to calculate the value of the serialVersionUID. The algorithm uses the name of the
serializable class, names of all the interfaces it implements, all the data
members of the serializable class, etc. You don't need to worry
about the details of this complex algorithm; the Java platform provides
a tool called serialver that calculates the value for you.
To establish the serialVersionUID for your MessagingPreferences object, you
can use the serialver tool from the command line as follows:
X:\jdk1.5\bin\serialver MessagingPreferences |
As you can see, I've already done this for the MessagingPreferences class shown in Listing 3.
Step 4. Store the Java object in ApacheDS
You now have the DirContext object and MessagingPreferences objects set up and ready to
go. What's left is to use the DirContext object to store the MessagingPreferences object in ApacheDS.
Storing a data entry in an LDAP server is called a bind operation.
The Context interface has a method named
bind() that you can use to store your Java
object in ApacheDS. You can see bind() at work in
Step 4 of Listing 1.
Setting the parameters of Context.bind()
The Context.bind() method takes two
parameters. The first parameter (cn=preferences,uid=alice,ou=users,ou=system)
specifies the named context in which you want to store your Java object.
This named context can be broken into two parts: cn=preferences and uid=alice,ou=users,ou=system, with a comma in
between.
Because the new entry represents the
viewing preferences of Alice, you use cn=preferences as its RDN. Note that the string uid=alice,ou=users,ou=system is same as the DN of the Alice's data entry, which you first saw in the "Creating an RDN" section of Part 1.
Based on all this, the DN of the new entry is cn=preferences,uid=alice,ou=users,ou=system, which
is the value of the first parameter you pass to the bind() method.
The second parameter of the Context.bind()
method call is the MessagingPreferences
object from Step 3. The bind() method
call does not return anything.
I've combined the four steps described above in the sample
application named StoreAlicePreferences shown
in Listing 1. You can also find the sample application in the source code for this article.
Before you run the StoreAlicePreferences application, you must have
an entry with a DN equal to uid=alice,ou=users,ou=system stored in ApacheDS.
You should have created a user named Alice in the "Creating an RDN" section of Part 1.
After running the StoreAlicePreferences
application, you can verify that it has stored Alice's messaging
preferences as a Java object by expanding the Alice entry in your LDAP
browser (in this case JXplorer). You should see an
expanded view of Alice, as shown in Figure 1:
Figure 1. Alice's messaging preferences have been stored!
Three attributes are included along with the
MessagingPreferences object shown in Figure 1. I discussed
these attributes -- javaClassName, javaClassNames, and javaSerializedData -- in the "Storing Java objects in ApacheDS" section of
Part 1.
I didn't include these attributes with the bind() method call in the StoreAlicePreferences application (in Step 4), so
you might wonder how they ended up in ApacheDS. The answer is that the bind() method itself wrote the attributes! The
two-parameter Context.bind() method
doesn't take any attributes. As I explained in the "Storing Java objects in ApacheDS" section
of Part 1, however, LDAP needs the javaClassName,
javaClassNames, and javaSerializedData attributes. So the Context.bind() method writes these attributes on
its own.
The next section introduces a three-parameter bind() method that takes an array of attributes
and stores them along with the Java object.
The MessagingPreferences
object shown in Figure 1 uses the javaContainer object class. I discussed this class in the
"Storing Java objects in ApacheDS" section of
Part 1. If you want, you can write a Java object to ApacheDS without using the javaContainer class, as my next
example application demonstrates.
Application 2. Store Java objects with attributes
In this example, you'll learn how to add attributes to your Java
object, using the three-parameter bind()
method I previously mentioned. Look at the sample application called
StoreBobPreferences in Listing 4. This
application creates an entry for a user named Bob and also stores
Bob's preferences (that is, attributes) in the entry, and it does both
steps in one go.
Listing 4. StoreBobPreferences
public class StoreBobPreferences{
public StoreBobPreferences ()
{
try {
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "apacheds.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//----------------------------------------------
//Step2: Fetching a DirContext object
//----------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//----------------------------------------------
//Step3A: Instantiate a Java Object
//----------------------------------------------
MessagingPreferences preferences = new MessagingPreferences();
//----------------------------------------------
//Step3B: Instantiate BasicAttribute object
//----------------------------------------------
Attribute objclass = new BasicAttribute("objectClass");
//----------------------------------------------
//Step3C: Supply value of attribute
//----------------------------------------------
objclass.add("person");
//----------------------------------------------
//Step3D: Put the attribute in attribute collection
//----------------------------------------------
Attributes attrs = new BasicAttributes(true);
attrs.put(objclass);
//----------------------------------------------
//Step4: Store the Java object in ApacheDS
//----------------------------------------------
String bindContext = "uid=Bob,ou=users";
ctx.bind( bindContext, preferences, attrs);
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
StoreBobPreferences storeBobPref = new StoreBobPreferences();
}
}
|
For the most part, Listing 4 consists of the same steps you saw in Listing 1, with the exception of some additional code in Step 3, which I'll explain below. (Note that Step 3A of Listing 4 is the same as Step 3 of Listing 1, so I'll start with Step 3B.)
Step 3B. Instantiate BasicAttribute
In the first step that differentiates the
StoreBobPreferences application from
StoreAlicePreferences, you instantiate a JNDI class named
BasicAttribute, which exposes a JNDI
interface called Attribute. The Attribute interface can represent a single
attribute of an LDAP data entry. The BasicAttribute class provides a basic
implementation (with limited functionality) of the Attribute interface. Applications and
implementations are encouraged to have their own implementation of the
Attribute interface (by extending the BasicAttribute class); however, the BasicAttribute class provides enough functionality
for the demonstration purposes of this article.
The BasicAttribute constructor takes the
name of the attribute as a parameter. Notice from Step 3B in Listing 4 that the first BasicAttribute object I have constructed takes
objectClass as its parameter. This means that
the BasicAttribute object represents an
attribute named objectClass.
You instantiate one instance of the BasicAttribute class for each of the attributes that
you want to add to the data entry for Bob.
Step 3C. Supply values to each attribute
When you have one BasicAttribute object for each of the attributes
you want to include with Bob's data entry, you supply
values to each of the attributes. To supply a value, you call the add() method of the Attribute interface. The add() method takes just one parameter,
which is a string form of the value you want to provide.
The add() method actually takes an
instance of the Java Object class. Because all Java objects extend from the Object class, you can pass string values to the add() methods. If some of your attributes are multivalued, you can call the add() method for that attribute any number of times to supply all the required values.
Step 3D. Create a collection of attributes
Now you have all the attributes, and you will put them together in a
collection of Attribute objects. JNDI provides an interface called Attributes and its basic implementation in a class
called BasicAttributes. You instantiate a
BasicAttributes object and call its put() method any number of times to put all the
Attribute objects (one at a time) into the
collection.
As shown in Step 4 of Listing 4, next you call a three-parameter version of the bind() method. The three-parameter bind() method is similar to the two-parameter
method used in Listing 1, with the third parameter
being the collection of attributes you just authored.
Application 3. Store a marshalled Java object
In this final exercise in storing Java objects, I'll show you how to store a marshalled Java object, which I discussed briefly in the "Representing a marshalled Java object" section of Part 1.
The Java object whose marshalled form you want to store should be
serializable (just like the serializable MessagingPreferences object that you created in
Step 3 of Listing 1). For the sake of
demonstration, I take this same MessagingPreferences object and show you how to
marshall and store it in ApacheDS.
First, have a look at the StoreAlicePreferencesInMarshalledForm
application in Listing 5, which shows all the steps to
store a marshalled Java object in ApacheDS:
Listing 5. StoreAlicePreferencesInMarshalledForm
public class StoreAlicePreferencesInMarshalledForm {
public StoreAlicePreferencesInMarshalledForm ()
{
try {
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//------------------------------------------
//Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//---------------------------------------------
//Step3: Instantiate a Java Object
//---------------------------------------------
MessagingPreferences preferences = new MessagingPreferences();
MarshalledObject mObj= new MarshalledObject(preferences );
//--------------------------------------------
//Step4: Storing Java object in ApacheDS
//--------------------------------------------
String bindContext = "cn=marshalledPreferences,uid=alice,ou=users";
ctx.bind( bindContext, mObj);
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
StoreAlicePreferencesInMarshalledForm storeAlicePref =
new StoreAlicePreferencesInMarshalledForm();
}
}
|
J2SE internally handles the marshalling process, so the application in Listing 5 is very similar to the StoreAlicePreferences application shown in Listing 1. If you compare the two applications,
you'll see that there is just one line of extra code in Step 3 of Listing 5. After instantiating the MessagingPreferences object, you also instantiate a
java.rmi.MarshalledObject, passing the preferences object to the java.rmi.MarshalledObject constructor. The java.rmi.MarshalledObject class then handles the
marshalling process and contains the marshalled version of your MessagingPreferences object.
In Step 4, you simply store (or bind) the
marshalled object instead of the original MessagingPreferences object.
That concludes my discussion of storing Java objects in ApacheDS. Now let's look at a couple of examples that demonstrate searching for data stored in ApacheDS.
Application 4. Search for stored data
I'll start with a simple search operation on ApacheDS. Suppose you have many users represented in your ApacheDS instance. You want to find all the details of a user named Alice. Everything you know about Alice is listed below:
- Alice is a user, so you should look for her data entry within the data organizational unit for users. (I introduced the concept of an organizational unit, or "ou," in Part 1.)
- Alice's user name is "alice" (case-insensitive).
- Alice is a person, so her data entry must use some object class
that directly or indirectly extends the
personobject class.
Now look at Listing 6, which shows a sample
application named SearchForAlice. SearchForAlice demonstrates a very simple search
scenario; later I extend this application to cover more advanced search scenarios.
Listing 6. SearchForAlice
public class SearchForAlice {
public SearchForAlice() {
try
{
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";
//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute uid = new BasicAttribute("uid");
Attribute objclass = new BasicAttribute("objectClass");
//adding attribute values
uid.add("Alice");
objclass.add("person");
//Instantiate Attributes object and put search attributes in it.
Attributes attrs = new BasicAttributes(true);
attrs.put(uid);
attrs.put(objclass);
//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);
if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();
//Step 8:
String entryRDN = sr.getName();
System.out.println("RDN of the Searched entry: "+entryRDN);
//Step 9:
Attributes srAttrs = sr.getAttributes();
if (srAttrs != null) {
//Step 10:
for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;)
{
Attribute attr = (Attribute) e.nextElement();
//Step 11:
String attrID = attr.getID();
System.out.println("Attribute Name: "+attrID);
System.out.println("Attribute Value(s):");
NamingEnumeration e1 = attr.getAll();
while (e1.hasMore())
System.out.println("\t\t"+e1.nextElement());
}//for()
}//if (srAttrs)
}
}//if (ne != null)
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
SearchForAlice searchAlice = new SearchForAlice();
}
}
|
The search application in Listing 6 consists of 11 steps. You'll recall the first two steps from Listing 1, namely loading JNDI properties and instantiating a DirContext object.
Recall that while
discussing the JNDI property named java.naming.provider.url in Step 1 in Listing 1, I mentioned that the provider
URL consists of two components, one of them being the
directory context in which you want to work. You'll notice that the
value of the java.naming.provider.url property in
Listing 4 is ou=system. The ou=system
string is the directory context in which you want to work. Therefore,
all search operations automatically execute within this directory
context.
Because in this example you are performing search operations,
you can call the ou=system directory context
your search context. Now let's look at the remaining steps of
the search application:
- Step 3. Narrow down your search context: You know that Alice
is a user, so instead of searching for Alice throughout the entire
ou=systemsearch context, you just search within the organizational unit for users, which isou=users. - Step 4. Author the search attributes: The information you
know about Alice becomes your search attributes. Because you know the uid and
object class for Alice, you author a collection of just
two attributes:
uidandobjectClass. You can see this in Step 4 of Listing 6. (You may recall from my discussion in Part 1 that theuidis a component of the RDN and not an attribute. When specifying search parameters, however, it is a JNDI requirement that you specify the uid value as if it were an attribute value.) - Step 5. Perform the search: Here you call the
search()method of theDirContextobject you got in Step 2 of Listing 6. Thesearch()method takes two parameters: the first parameter is the search context you authored in Step 3 of this exercise and the second parameter is the collection of two attributes from Step 4. Thesearch()method returns aNamingEnumerationobject, which contains your search results.
Steps 1 through 5 set up the search operation. The remaining steps process the NamingEnumeration object and extract the search results.
- Step 6. Fetch search results: The
NamingEnumerationobject in Step 6 of Listing 6 contains a collection of search results. Each search result in the collection is represented by aSearchResultobject. To fetch individual search results, you just need to iterate through theNamingEnumerationobject. - Step 7. Process an individual search result: Note that
each search result contains information about a single data entry. You
can get two pieces of information (that is, the RDN and all its attributes)
about the data entry from the
SearchResultobject. - Step 8. Call getName(): The
getName()method of theSearchResultobject returns the RDN of the entry you're searching. The RDN for Alice isuid=alice. - Step 9. Call getAttributes(): The
getAttributes()method of theSearchResultobject returns anAttributesobject, which contains all attribute values associated with the entry you're searching. TheAttributesobject represents a collection of attributes similar to the collection of attributes you authored in Step 4 of Listing 6. - Step 10. Call getAll(): The
getAll()method of theAttributesobject returns an enumeration containing all attributes in the collection. - Step 11. Process attributes: Finally, you take one attribute out of the collection and call its
getID()andgetAll()methods. ThegetID()method returns the name of the attribute as a string. ThegetAll()method returns all values of the attribute in the form of an enumeration.
In the previous search example, you saw how to search for a user if
you know the user's uid. In this example, you'll learn
how to modify the application to search for Alice
using her name instead of her uid.
Recall from Part 1, Figure
18 that a user's name is stored as the
value of the cn attribute of the user's data
entry. Therefore, in this application, you search for the cn attribute, as shown in Listing 7:
Listing 7. SearchForAliceByCN
public class SearchForAliceByCN {
public SearchForAliceByCN() {
try
{
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";
//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute cn = new BasicAttribute("cn");
Attribute objclass = new BasicAttribute("objectClass");
//putting attribute values
cn.add("Alice");
objclass.add("person");
//Instantiate an Attributes object and put search attributes in it
Attributes attrs = new BasicAttributes(true);
attrs.put(cn);
attrs.put(objclass);
//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);
if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();
//Step 8:
String entryRDN = sr.getName();
//Step 9:
Attributes srAttrs = sr.getAttributes();
if (srAttrs != null) {
//Step 10:
for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;)
{
Attribute attr = (Attribute) e.nextElement();
//Step 11:
String attrID = attr.getID();
System.out.println("Attribute Name: "+attrID);
System.out.println("Attribute Value(s):");
NamingEnumeration e1 = attr.getAll();
while (e1.hasMore())
System.out.println("\t\t"+e1.nextElement());
}//for()
}//if (srAttrs)
}
}//if (ne != null)
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
SearchForAliceByCN searchAlice = new SearchForAliceByCN();
}
}
|
The SearchForAliceByCN
application shows the steps to search for Alice using her name. The
application is very similar to the previous SearchForAlice application, with just one difference. In Step 4 of Listing 6, you authored a
collection of uid and objectClass attributes for the search. In Step 4 of this application,
you will instead author a collection of cn and objectClass attributes.
There is one important point to notice about searching for cn. You saw in Part 1, Figure 13 that the cn attribute type has a
field called SUBSTR, which defines the
matching rules for substring match.
In the case of a cn attribute, the value
of the SUBSTR field is caseIgnoreMatch, so when you search for a
specific value of the cn attribute, a match
is considered successful even if the name being searched for
matches with a substring in the cn attribute
value. Moreover, the substring match is case-insensitive.
Therefore, if you search for "alice," all users with first, middle, or last name "Alice" are included in the search results.
Application 6. Deserialize a Java object
You've seen how to store a Java object in ApacheDS and search for attributes associated with a stored object. Now you'll learn how to search for and deserialize a Java object. Deserialization is the opposite of serialization, where you create a Java object from its serialized form.
The application shown in Listing 8 searches for and
deserializes Alice's MessagingPreferences object. Recall that you stored
the MessagingPreferences object in ApacheDS
back in Listing 1.
The FetchAliceMessagingPreferences application
is an enhanced version of the SearchForAliceByCN application seen in Listing 7. In fact, Listing 8 is the
same as Listing 7 up to Step 8, where you
extract the RDN of the Alice's data entry. You start looking for
Alice's Preferences object after Step 8
in Listing 8:
Listing 8. FetchAliceMessagingPreferences
public class FetchAliceMessagingPreferences {
public FetchAliceMessagingPreferences() {
try
{
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";
//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute cn = new BasicAttribute("cn");
Attribute objclass = new BasicAttribute("objectClass");
//putting attribute values
cn.add("Alice");
objclass.add("person");
//Instantiate an Attributes object and put search attributes in it
Attributes attrs = new BasicAttributes(true);
attrs.put(cn);
attrs.put(objclass);
//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);
if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();
//Step 8:
String entryRDN = sr.getName();
//---------------------------------------------
//Step9: Setting a new search context
//---------------------------------------------
searchContext = entryRDN + "," + searchContext;
//---------------------------------------------
//Step10: Creating search controls
//---------------------------------------------
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//---------------------------------------------
//Step11: Creating filter
//---------------------------------------------
String filter = "(|(javaClassName=MessagingPreferences)
(javaClassName=ShippingPreferences))";
//------------------------------------------
//Step12: Executing search
//------------------------------------------
NamingEnumeration ne1 = ctx.search(searchContext, filter, ctls);
if (ne != null)
{
//Step13: Iterating through SearchResults
while (ne1.hasMore()) {
//Step14: Getting individual SearchResult object
SearchResult sr1 = (SearchResult) ne1.next();
//Step15: Getting preferences object
Object obj = sr1.getObject();
if (obj != null) {
if (obj instanceof MessagingPreferences){
MessagingPreferences pref =
(MessagingPreferences) obj;
}
else if (obj instanceof ShippingPreferences) {
ShippingPreferences pref = (ShippingPreferences) obj;
}
}
}//while(
}//if
}//while
}//if (ne != null)
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
FetchAliceMessagingPreferences searchAlicePreferences =
new FetchAliceMessagingPreferences();
}
}
|
Before going through the steps to search for and deserialize Alice's MessagingPreferences object, you may want to
revisit Figure 1, which shows that Alice's
MessagingPreferences object resides inside
the data entry for Alice. Therefore, you need to look for the MessagingPreferences object inside the data entry
for Alice.
How do you "look inside" a data entry? Well, we use the concept of
the search context specifically for this purpose. In this case,
you'll need to narrow down the search context that I introduced with the
first search application, SearchForAlice,
shown in Listing 6.
You do this in Step 9 of Listing 8, where you concatenate the RDN of Alice (uid=alice) with the original search context you used to search for Alice's data entry (ou=users,ou=system). The resulting search context (uid=alice,ou=users,ou=system) makes it possible to
look inside Alice's data entry.
Now let's go through the remaining steps of the FetchAliceMessagingPreferences application.
Building and using search controls
In Step 10 of Listing 8, you instantiate a SearchControls object, which you can then
use to build some search controls. Search controls are used for
two main purposes:
- To specify the type of data search results contain. In this case, you want to receive a Java object, so
you call a
setReturningObjFlag()method of theSearchControlsobject. This method sets a flag in the search controls to specify that a search is being carried out to fetch an object. - To specify the scope of search. "Scope
of search" means whether you want to search within a particular data
entry or you want to search for one or more levels below the entry as
well. You set the search scope by calling the
setSearchScope()method of theSearchControlsobject.
In Step 11 of Listing 8, you author a string named "filter." You can see that value of the filter string is (|(javaClassName=MessagingPreferences)
(javaClassName=ShippingPreferences)). The two attribute-value
pairs in brackets specify different values for a single attribute named
javaClassName. Also notice the "OR"
before the two attribute-value pairs. This means you're looking for
either of the MessagingPreferences or ShippingPreferences objects.
This filter string acts as a filter for search results. This means the search results returned after the search operation will contain only those results that fulfill the criteria specified in the search filter.
You use this type of search filter when you are looking for either of a number of attribute values. For more details on search filters, refer to the Resources section, which contains a link to an article introducing the concept of search filters in LDAP applications.
Fetching and processing search results
In Step 12 of Listing 8, you call the
search() method, passing the search context,
search filter, and search controls along with the method call.
Note the difference between the search method call used in Step 5 of Listing 6 and the one used in
Listing 8: In Listing 6, you use a two-argument form of the search() method, and in Listing 8 you use the three-argument overloaded form of the same method.
Steps 13 and 14 of Listing 8 are the same as
Steps 6 and 7 of Listing 7, respectively. In these steps, you process the NamingEnumeration object
and fetch an individual search result in the form of a SearchResult object.
Finally, in Step 15, you call the getObject()
method of the SearchResult object. The getObject() method returns a Java object.
You cannot be sure of the class whose instance is returned by the getObject() method because you specify two classes
(MessagingPreferences or ShippingPreferences) in the search request. The returned
result could be an instance of either of the two classes. As a result, you have to first check which instance the object
belongs to and then cast the object accordingly. Once you've done this, the Java object is in your control and you can call its methods.
Application 7. Unmarshall a Java object
In the previous application, you learned how to deserialize a serialized Java object. Next, with the application FetchAliceMarshalledPreferences, you'll learn how to unmarshall a marshalled Java object.
Listing 9. FetchAliceMarshalledPreferences
public class FetchAliceMarshalledPreferences {
public FetchAliceMarshalledPreferences() {
try
{
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";
//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute cn = new BasicAttribute("cn");
Attribute objclass = new BasicAttribute("objectClass");
//putting attribute values
cn.add("Alice");
objclass.add("person");
//Instantiate an Attributes object and put search attributes in it
Attributes attrs = new BasicAttributes(true);
attrs.put(cn);
attrs.put(objclass);
//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);
if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();
//Step 8:
String entryRDN = sr.getName();
System.out.println("RDN of the Searched entry: "+entryRDN);
//---------------------------------------------
//Step9: Setting a new search context
//---------------------------------------------
searchContext = entryRDN + "," + searchContext;
System.out.println("new SearchContext: "+searchContext);
//---------------------------------------------
//Step10: Creating search controls
//---------------------------------------------
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//---------------------------------------------
//Step11: Creating filter
//---------------------------------------------
String filter = "(javaClassName=java.rmi.MarshalledObject)";
//------------------------------------------
//Step12: Executing searchl
//------------------------------------------
NamingEnumeration ne1 = ctx.search(searchContext, filter, ctls);
if (ne != null)
{
//Step13: Iterating through SearchResults
while (ne1.hasMore()) {
//Step14: Getting individual SearchResult object
SearchResult sr1 = (SearchResult) ne1.next();
//Step15: Getting preferences object
Object obj = sr1.getObject();
if (obj instanceof MarshalledObject) {
MarshalledObject mObj= (MarshalledObject) obj;
Object obj1 = mObj.get();
if (obj1 instanceof MarshalledObject) {
MessagingPreferences pref =
(MessagingPreferences) obj;
}
else if (obj1 instanceof ShippingPreferences) {
ShippingPreferences pref =
(ShippingPreferences) obj;
}
}//if(obj)
}//while
}//if
}//while
}//if (ne != null)
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
FetchAliceMarshalledPreferences fetchAlicePref =
new FetchAliceMarshalledPreferences();
}
}
|
Listing 9 contains 15 steps, the first 14 of
which are all same as the first 14 steps you saw in Listing 8. The only difference between the two applications
is Step 15 of Listing 9, in which the SearchResult.getObject() method returns a marshalled
object. You can confirm this yourself by checking the class name of the returned
object. After checking it, you cast the object as a MarshalledObject.
The serialized form of your Java object resides inside this
marshalled object, so you now call the get()
method of the MarshalledObject class to fetch
the Java object, which you can cast after confirming its class.
Application 8. Edit and update stored objects
With the examples so far, I've focused on storing and searching Java objects. The next application shows you how to edit and update a Java object already stored in ApacheDS.
The UpdateAlicePreferences application in Listing 10 updates the
Java object that you mixed with the person
object class back in Listing 4:
Listing 10. UpdateAlicePreferences
public class UpdateAlicePreferences {
public UpdateAlicePreferences() {
try
{
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");
//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);
//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";
//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute cn = new BasicAttribute("cn");
Attribute objclass = new BasicAttribute("objectClass");
//putting attribute values
cn.add("Alice");
objclass.add("person");
//Instantiate an Attributes object and put search attributes in it
Attributes attrs = new BasicAttributes(true);
attrs.put(cn);
attrs.put(objclass);
//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);
if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();
//Step 8:
String entryRDN = sr.getName();
//---------------------------------------------
//Step9: Setting a new search context
//---------------------------------------------
searchContext = entryRDN + "," + searchContext;
//---------------------------------------------
//Step10: Creating search controls
//---------------------------------------------
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//---------------------------------------------
//Step11: Creating filter
//---------------------------------------------
String filter = "(javaClassName=java.rmi.MarshalledObject)";
//------------------------------------------
//Step12: Executing search
//------------------------------------------
NamingEnumeration ne1 = ctx.search(searchContext, filter, ctls);
if (ne != null)
{
//Step13: Iterating through SearchResults
while (ne1.hasMore()) {
//Step14: Getting individual SearchResult object
SearchResult sr1 = (SearchResult) ne1.next();
//Step15: Getting preferences object
Object obj = sr1.getObject();
entryRDN = sr1.getName();
if (obj != null && obj instanceof MarshalledObject)
{
MarshalledObject mObj= (MarshalledObject) obj;
MessagingPreferences pref =
(MessagingPreferences) mObj.get();
//Step16: Updating your java object
pref.setStyles("http://www.mystyles.com/preferences");
//Step17: Setting a new context for data updation
String bindContext = entryRDN + "," + searchContext;
//Step18: Reading attributes in a new collection
Attributes attrs1 = sr1.getAttributes();
//Step19:Updating data
ctx.rebind(bindContext, pref, attrs);
System.out.println("Updating
the MessagingPreferences succeed");
}
}//while
}//if
}//while
}//if (ne != null)
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}
public static void main(String[] args) {
UpdateAlicePreferences updateAlicePreferences =
new UpdateAlicePreferences();
}
}
|
The first 15 steps of Listing 10 should be familiar to you from the previous exercises. In these steps, you're simply searching for and fetching the Java object that you want to update. In the steps that follow, you update and store the object.
Updating and storing a Java object
In Step 16, you update the data contained in your Java object by calling its methods. Once you've updated the data, you want to store the edited version of your Java object. Before you can do that, you need to do two things:
- Get the named context: Updating an existing entry means you want to write your updated data to the same named context on which it was previously written. For this you need to know the named context of the entry being updated. The named context of a Java object can be formed by concatenating the search context (which you used to search for your object) with the RDN of the object. You can see this in Step 17 of Listing 10.
- Write all attributes to the updated Java object: When you write the updated Java object back into ApacheDS, it is written as a new data entry on the same named context. All attributes associated with the data entry are lost. So, you need to write all the attributes of your data entry back into the updated Java object. In Step 18 of Listing 10, I've handled this by reading all the attributes from the search result and wrapping them in a collection of attributes.
Finally in Step 19, you call DirContext.rebind() method. The rebind() method takes exactly the same parameters
that you passed to the three-parameter bind()
method in Step 4 of Listing 4, the StoreBobPreferences application.
The only difference between the bind()
and rebind() methods is that the rebind() method stores a data entry at a named
context already occupied by an existing data entry, effectively updating
the existing entry with new data.
Application 9. Pulling it all together
I'll conclude this article with a final application that combines all the concepts you've learned so far into a simple, reusable object that can store, search, delete, and update Java objects in ApacheDS. The LDAP4JavaObjects class is shown in Listing 11:
Listing 11. LDAP4JavaObjects
public class LDAP4JavaObjects {
protected String commonName = null;
protected String surName = null;
protected String userID = null;
protected String javaObjectCN = null;
protected String userName = null;
protected String password = null;
protected String initialContext = null;
private String workingContext = null;
protected DirContext dirContext = null;
protected Properties properties = null;
protected Object javaObject = null;
protected Attributes attributes = null;
public LDAP4JavaObjects() {
properties = new Properties();
attributes = new BasicAttributes(true);
workingContext = "ou=users";
initialContext = workingContext;
try {
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
properties.load(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}//LDAPJavaObjects
public void setUserName(String userName){
properties.setProperty("java.naming.security.principal", userName);
this.userName = userName;
}
public void setPassword(String password) {
properties.setProperty("java.naming.security.credentials", password);
this.password = password;
}
protected void connect() {
try {
// Fetch the directory context.
dirContext= new InitialDirContext(properties);
} catch (NamingException e) {
System.out.println("Getting initial context operation failed: " + e);
}
}
protected void close() {
try {
// Close the directory context.
dirContext.close();
} catch (NamingException e) {
System.out.println("Closing initial context operation failed: " + e);
}
}
public void setJavaObject(Object obj) throws java.io.NotSerializableException {
if (obj instanceof java.io.Serializable)
javaObject = obj;
}
protected boolean isObjectPresent(String uid, String cn, String workContext) {
NamingEnumeration ne = search(uid, cn, workContext);
if (ne != null)
return true;
else
return false;
}//isObjectPresent
protected NamingEnumeration search(String user, String object, String workContext) {
NamingEnumeration ne = null;
String filter = new String();
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
try {
if (user != null && object == null) {
addUserAttribute ("uid", user);
addUserAttribute ("objectClass", "person");
ne = dirContext.search(workContext, attributes);
if (ne != null && ne.hasMore())
return ne;
else
return null;
}else if (user == null && object != null)
{
filter = "(cn="+object+")";
ctls.setReturningObjFlag(true);
ne = dirContext.search(workContext, filter, ctls);
if (ne != null && ne.hasMore())
return ne;
else
return null;
}
} catch (NamingException e) {
e.printStackTrace();
}
return null;
}//search
protected void update(String uid, String cn, String workContext) {
if (uid != null && cn == null)
{
String[] objClassValues = {"top","person"};
addUserAttribute ("uid", uid);
if (commonName != null)
addUserAttribute ("cn", commonName);
addUserAttributes ("objectClass", objClassValues);
try {
dirContext.rebind(workContext, null, attributes);
} catch (NamingException e) {
System.out.println("Storing operation failed: " + e);
}
} else if (uid == null && cn != null) {
addUserAttribute ("cn", cn);
try {
dirContext.rebind(workContext, javaObject, attributes);
} catch (NamingException e) {
e.printStackTrace();
}
}
}//update
protected void store(String uid, String cn, String workContext) {
if (uid != null && cn == null)
{
String[] objClassValues = {"top","person"};
addUserAttribute ("uid", uid);
if (commonName != null)
addUserAttribute ("cn", commonName);
addUserAttributes ("objectClass", objClassValues);
try
{
dirContext.bind(workContext, null, attributes);
} catch (NamingException e) {
e.printStackTrace();
}
} else if (uid == null && cn != null) {
addUserAttribute ("cn", cn);
try {
dirContext.bind(workContext, javaObject, attributes);
} catch (NamingException e) {
e.printStackTrace();
}
}
}//store
public void addUserAttribute (String name, String value){
Attribute attr = new BasicAttribute(name);
attr.add(value);
attributes.put(attr);
}//addUserAttribute
public void addUserAttributes(String name, String[] value) {
Attribute attr = new BasicAttribute (name);
if (value != null) {
for (int i =0; i < value.length; i++)
attr.add(value[i]);
}
attributes.put(attr);
}//addUserAttribute
public void writeToServer () {
connect();
if (userID == null && commonName != null)
userID = commonName;
//Check if the cn of the Java object is set.
//If not, use commonName as object cn.
if (javaObject != null)
{
if (javaObjectCN == null && commonName != null)
javaObjectCN = commonName;
}
if (!(isObjectPresent(userID, null, initialContext)))
{
workingContext = "uid="+userID+","+initialContext;
store(userID, null, workingContext);
workingContext = "cn="+javaObjectCN +",uid="+userID+","+initialContext;
store( null, javaObjectCN, workingContext);
} else if (isObjectPresent(null, javaObjectCN,
"uid="+userID+","+initialContext)) {
workingContext = "cn="+javaObjectCN +",uid="+userID +","+ initialContext;
update(null, javaObjectCN, workingContext);
} else {
workingContext = "cn="+javaObjectCN +",uid="+userID +","+ initialContext;
store(null, javaObjectCN, workingContext);
}
close();
}//writeToServer()
public void searchFromServer(String user, String object) {
connect();
NamingEnumeration ne = search(user, object, initialContext);
if (ne != null)
processSearchResult(ne);
else
System.out.println ("searchFromServer... failed");
close();
}//searchFromServer
private void processSearchResult (NamingEnumeration ne){
try {
if (ne!=null)
{
while (ne.hasMore()) {
SearchResult sr = (SearchResult) ne.next();
Object obj = sr.getObject();
if (obj != null) {
javaObject = obj;
javaObjectCN = sr.getName();
} else {
userID = sr.getName();
processAttributes(sr.getAttributes());
}
}//while
}//if (ne != null)
} catch (javax.naming.NamingException e) {
e.printStackTrace();
}
}//processSearchResult
private void processAttributes(Attributes attrs){
try {
for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;) {
Attribute attr = (Attribute) e.nextElement();
if (attr.getID().equals("cn"))
commonName = (String)attr.get();
else if (attr.getID().equals("sn"))
surName = (String)attr.get();
}
} catch (javax.naming.NamingException ne){
ne.printStackTrace();
}
}//processAttributes
public void setUserID(String userID) {
this.userID = userID;
}
public String getUserID() {
return userID;
}
public void setJavaObjectCN(String objectCN) {
this.javaObjectCN = objectCN;
}
public String getJavaObjectCN() {
return javaObjectCN;
}
public void setCommonName (String cn) {
commonName = cn;
}
public void setSurName (String sn) {
surName = sn;
}
public String getCommonName() {
return commonName;
}
public String getSurName() {
return surName;
}
public static void main(String[] args) {
LDAP4JavaObjects javaLdapClient = new LDAP4JavaObjects();
javaLdapClient.setPassword("secret");
javaLdapClient.setUserID("Alice");
//Instantiating a Java object.
MessagingPreferences msgPreferences =
new MessagingPreferences ();
//Setting a Java object.
try {
javaLdapClient.setJavaObject (msgPreferences);
} catch (Exception e) {
e.printStackTrace();
}
javaLdapClient.setJavaObjectCN ("preferences");
javaLdapClient.writeToServer();
System.out.println ("Entry stored in ApacheDS..");
}//main
}
|
The first thing you'll notice about the LDAP4Java class
is that it has methods to manage a data entry for a user that belongs to
the person object class. The setUserName() and setPassword() methods set the username and password
for the application.
After setting the server address, you can use setter methods like
setCommonName() and setSurName(). These setter methods set the
attribute values of the user. In addition to setCommonName() and setSurName() methods, LDAP4JavaObjects also contains a addUserAttribute() method. You can use the addUserAttribute() method to specify additional
user attributes.
After setting the attribute values, you can call the setJavaObject() method to set your Java object in
LDAP4JavaObjects. You can use the setContext() method to set the
server context in which you want to write or search for a Java object.
After supplying all the required data to LDAP4JavaObjects, you can call the writeToServer() method. This method is intelligent.
It first looks to see if the user's entry is present in the remote
server or not. If it cannot find the entry, it writes a new one. If the
entry is already in the server, it updates the entry.
LDAP4JavaObjects also has a method called
searchFromServer(), which you can use to search
for user data or Java objects in ApacheDS. The searchFromServer() method updates LDAP4JavaObjects data members after the search
operation.
You can extend the LDAP4JavaObjects
application to suit the business logic of your applications. All the low-level JNDI logic is written in protected methods of LDAP4JavaObjects for just this reason. You can use the protected methods in your own class that extends LDAP4JavaObjects.
You can run the main() method in Listing 11 if you want to see a demonstration of
the use of all these LDAP4JavaObjects methods.
In this two-part article, you've learned how ApacheDS provides server-side LDAP functionality. In addition to a background discussion on the directory services architecture of ApacheDS and how it functions as an LDAP server, I've provided nine example applications demonstrating how to store, search retrieve, and update Java objects in ApacheDS.
The complete source code for this article includes JNDI code samples that you can use to work with your Java objects in ApacheDS, as well as a reusable class that contains all the functionality discussed. You can use this class to practice manipulating Java objects in ApacheDS, and you can also extend it in your own Java applications.
See Resources to learn more about ApacheDS and related technologies discussed in this article.
| Description | Name | Size | Download method |
|---|---|---|---|
| Source code | j-apachedssource.zip | 40KB | HTTP |
Information about download methods
Learn
- "Storing Java objects in Apache Directory Server, Part 1" (Bilal Siddiqui, developerWorks, May 2006): Introduces ApacheDS and provides an overview of its core architecture.
- "Introduction to LDAP, Part 1: Installation and simple Java LDAP Programming" (Fred
Simmons and Jeng Yoong Tan, developerWorks, April 2005): Introduces the concept of search filters in LDAP.
- Lightweight Directory Access Protocol, v3 (RFC 2251): Read the official LDAP specification.
- Schema for Representing Java Objects in an LDAP Directory (RFC 2713): Defines the schema for representing Java objects in an LDAP directory.
- The Internet Engineering Task Force hosts the complete RFC's of the
LDAP family of specifications, as follows:
- RFC 2256 details the user's schema for LDAPv3.
- RFC 2798 elaborates attribute syntax definitions for LDAP entries.
- RFC 2253 describes UTF-8 string representation for distinguished names in LDAP.
- RFC 2254 describes representation of search filters in LDAP.
- The RMI tutorial trail (Ann Wollrath and Jim Waldo, Java.sun.com): A step-by-step introduction to many aspects of RMI.
- The JNDI tutorial: A good place to get started with JNDI.
- Java Object Serialization and RMI specifications: Get the information directly from the source.
- CS 696 Emerging Technologies: Distributed Objects (Course notes, San Diego State University, April 1998): A student's guide to serialization and marshalling.
- The Java technology zone: Hundreds of articles about every aspect of Java programming.
Get products and technologies
- Apache Directory Server: Download it from Apache.org.
- JXplorer: The Java-based, open-source LDAP client used extensively in this
article.
- The Internet Assigned Numbers Authority: Get a free OID for your enterprise.
- Softerra LDAP Browser: A free LDAP browser.
Discuss
- Open Group forums: Discuss directory interoperability issues.
- developerWorks blogs: Get involved in the developerWorks community.
Bilal Siddiqui is an electronics engineer, an XML consultant, and the co-founder of WaxSys, a company focused on simplifying e-business. After graduating in 1995 with a degree in electronics engineering from the University of Engineering and Technology, Lahore, he began designing software solutions for industrial control systems. Later, he turned to XML and used his experience programming in C++ to build Web- and Wap-based XML processing tools, server-side parsing solutions, and service applications. Bilal is a technology evangelist and a frequently-published technical author.
Comments (Undergoing maintenance)





