Skip to main content

Storing Java objects in Apache Directory Server, Part 2

Store, search, and retrieve Java objects in ApacheDS

Bilal Siddiqui (xml4java@yahoo.co.uk), Freelance consultant, WaxSys
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.

Summary:  In this second half of his introduction to storing Java™ objects in Apache Directory Server (ApacheDS), Bilal Siddiqui presents nine example applications to demonstrate the concepts you learned in Part 1. In addition to walking you through all the steps to store, search, retrieve, and modify Java objects using ApacheDS, Bilal concludes the article with a reusable Java class that combines these functions using LDAP schema components in ApacheDS.

Date:  02 May 2006
Level:  Intermediate
Activity:  1825 views

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.

Don't get lost!

Note that you must understand basic LDAP terminology and concepts, such as Distinguished Name (DN), Relative Distinguished Name (RDN), named context, object class, and attribute type, to follow the examples in this article. If you're unfamiliar with these terms, read the first half of this article before proceeding.

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.

Name-value pairs

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.

Setting the user password

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

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.

Advantages of using ApacheDS

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.

Getting the serialVersionUID

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.


Run the first application!

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!

Application notes

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 person object 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=system search context, you just search within the organizational unit for users, which is ou=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: uid and objectClass. You can see this in Step 4 of Listing 6. (You may recall from my discussion in Part 1 that the uid is 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 the DirContext object you got in Step 2 of Listing 6. The search() 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. The search() method returns a NamingEnumeration object, 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 NamingEnumeration object in Step 6 of Listing 6 contains a collection of search results. Each search result in the collection is represented by a SearchResult object. To fetch individual search results, you just need to iterate through the NamingEnumeration object.

  • 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 SearchResult object.

  • Step 8. Call getName(): The getName() method of the SearchResult object returns the RDN of the entry you're searching. The RDN for Alice is uid=alice.

  • Step 9. Call getAttributes(): The getAttributes() method of the SearchResult object returns an Attributes object, which contains all attribute values associated with the entry you're searching. The Attributes object 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 the Attributes object 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() and getAll() methods. The getID() method returns the name of the attribute as a string. The getAll() method returns all values of the attribute in the form of an enumeration.


Application 5. Search by name

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.

About matching rules

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();
    }
 }      

Search context revisited

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 the SearchControls object. 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 the SearchControls object.

Filtering search results

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
 }      


Notes about the application

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.


Conclusion to Part 2

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.



Download

DescriptionNameSizeDownload method
Source codej-apachedssource.zip40KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

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)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=109895
ArticleTitle=Storing Java objects in Apache Directory Server, Part 2
publish-date=05022006
author1-email=xml4java@yahoo.co.uk
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers