JNDI overview, Part 4: the Doc-u-Matic, a JNDI application

Pull together your JNDI knowledge with a JNDI-enabled application

Let's dive in. I've spent the last three months describing the Java Naming and Directory Interface (JNDI). By now, you should feel comfortable with naming and directory services and the operations they support. It's now time to build on that foundation. I've developed a JNDI-enabled document publication and distribution application -- called Doc-u-Matic -- that illustrates and utilizes the material I've presented in the past three How-To Java columns.

The Doc-u-Matic application illustrates JNDI's support for:

  • Centralized information administration
  • Network-wide information distribution
  • Object persistence

Note: To download the complete source code for this article, see Resources.

Keep the objects around

Doc-u-Matic is first-and-foremost a demonstration of JNDI-supported object persistence. Therefore, I think it makes sense to begin with a review of JNDI's support for stored objects.

You may recall from March's column that there are three techniques for storing Java objects in a JNDI service: as serialized data, as a reference to an object, and as the attributes on a directory context. Storing an object as serialized data is the simplest of the three techniques. Storing an object as a reference is useful in situations in which it doesn't make sense (or isn't possible) to store actual objects. Finally, storing objects as attributes on a directory context is useful when other, non-Java applications need access to the object's information. The application I've developed uses the first two methods of object storage.

The functional units

Figure 1, below, illustrates Doc-u-Matic from a functional perspective. Doc-u-Matic consists of three major functional units: the JNDI service (of course), a library service, and one or more clients.

Figure 1. Doc-u-Matic: A functional view

A library is a place to which you publish objects and from which you retrieve them. An object can be any Java instance, as long as it supports one of the storage methods mentioned above -- it can be a String, a JavaBean, and so on.

JNDI plays two roles in Doc-u-Matic. It provides a single, well-known location where clients can locate the library service (standard address-book functionality), and it supports the implementation of the library service itself. On the latter point, it's worth noting that nothing in the design of a library implies that it has to be implemented on top of JNDI -- that's just the direction I took (since this is an article on JNDI). You could implement a library on top of any technology that provides support for publishing and retrieving information, including HTTP, JDBC, NNTP, or IMAP. Best of all, the clients would never know the difference.

Before you proceed...

Doc-u-Matic assumes you have obtained, or have access to, and have configured an LDAP service. The application should support any naming and directory service that provides a JNDI service provider. However, I will assume you're using LDAP.

Before you proceed, make sure you've obtained, installed, and configured the following (if necessary, refer back to my earlier columns):

  • An LDAP implementation
  • Sun's JNDI reference implementation and the LDAP service provider

You'll also need to create the initial context that the application will connect to. I assume the following:

  ou=HowTo,o=JavaWorld

If you're not sure how to do this, you'll need to refer to your LDAP service's documentation.

Usage

Before we dive into the code, let's stop for a moment and look at how to use Doc-u-Matic.

There are two usage roles: the administrator and the user. The administrator (think of a librarian) creates or deploys the library and publishes objects of various types in the library. The administrator also creates and distributes a properties file that contains all of the information necessary to connect to and use the library. Users, on the other hand, retrieve objects from the library.

The administrator's role

Let's begin by assuming the role of the administrator. The deployment tool is named JNDIDeploy. Before you deploy a library, you must create a properties file that contains the information necessary to connect to an initial context. The properties file must also contain the name that the JNDILibrary object will be bound to. If you configured your LDAP service with the LDIF I've supplied (see Resources), the following properties file will provide a good place to start:

# DEPLOYMENT PROPERTIES
# This properties file contains all of the information necessary to
# find and connect to the JNDI service that holds the library and all
# published objects.
java.naming.factory.initial = com.sun.jndi.ldap.LdapCtxFactory
java.naming.provider.url = ldap://localhost:389/ou=HowTo,o=JavaWorld
library.name = cn=library

The library deploys as follows (I assume you've already set up the classpath to point to the jndi.jar and ldap.jar JAR files):

  java JNDIDeploy <properties file>

Once you, as administrator, have deployed a library, you must distribute a properties file to all users. The client applications I've developed all read a properties file pointed to by a URL. Therefore, the easiest way to distribute this file is to put it on a Web server and distribute the URL of the properties file.

Once you have deployed a library, you can publish objects to the library. By objects, I mean instances of Java classes. Objects play the role of abstract containers of information. As such, the simplest object is probably an instance of the String class. I've purposefully placed few requirements on publishable objects; they must be instantiateable via the Beans.instantiate() method, and they must be storable via JNDI. Objects are published as follows:

  java Publish <properties URL> [name class]...

The Publish command requires the URL of the properties file mentioned above. It also accepts any number of additional pairs of arguments. The first value in each pair is the name of the object. The second is the name of the class (including package information) that will be instantiated and stored. The name must conform to whatever naming policy the JNDI service provider requires. In the case of LDAP, names will be of the form:

  <key>=<value>

The user's role

Now, remove your administrator's hat and don your user's hat. It's time to retrieve some of the objects we've published. As a user, the only facts we have to know to retrieve an object are its name and the URL that describes the location of the library. We don't have to know how the library is implemented. This is especially useful if we, as users, must write programs to use the library. As you'll see a bit later, the code required to use a library is small indeed.

I've supplied a small client program that retrieves objects from the library. Objects are retrieved as follows:

  java Client <properties URL> [name]...

The Client command requires the URL of the properties file. It also accepts any number of additional arguments specifying the names of objects to retrieve from the library.

The code

Now, let's get to the core of this article -- the code. Figure 2, below, depicts the six classes that form the core of Doc-u-Matic. I'll visit each, briefly describe its role, and then present the important bits of the code (see Resources to download each class's complete source code).

Figure 2. Doc-u-Matic's six core classes

Library.java

The Library interface defines the methods that all libraries must provide. For the sake of simplicity, it requires only two methods: one to publish objects and one to retrieve objects. Full-featured libraries would provide search functionality as well.

  /**
   * Publishes an object in the library.
   *
   * @param object the object
   * @param stringName the name of the object
   * @param map a map containing the object's attributes
   *
   */
  public
  void
  publish(Object object, String stringName, Map map);
  /**
   * Retrieves a copy of a published object from the library
   * and restores it if necessary.
   *
   * @param stringName the name of the object
   *
   * @returns the object, or null
   *
   */
  public
  Object
  retrieve(String stringName);

JNDILibrary.java

The JNDILibrary class provides a JNDI-based implementation of the Library interface. The publish() and retrieve() methods demonstrate how to use JNDI to bind objects to and look up objects from a directory service.

  /**
   * Publishes an object in the library.
   *
   * @param object the object
   * @param stringName the name of the object
   * @param map a map containing the object's attributes
   *
   */
  public
  void
  publish(Object object, String stringName, Map map) {
    try {
      // Use the properties to establish an initial directory context.
      DirContext dircontext = new InitialDirContext(m_properties);
      // Create an iterator that contains all the entries in the map.
      Iterator iterator = map.entrySet().iterator();
      // For each entry, create an attribute.
      BasicAttributes basicattributes = new BasicAttributes();
      while (iterator.hasNext()) {
        Map.Entry entry = (Map.Entry)iterator.next();
        basicattributes.put(entry.getKey().toString(), 
entry.getValue().toString());
      }
      // Bind the object to the specified name.
      dircontext.rebind(stringName, object, basicattributes);
      // Close the context.
      dircontext.close();
    }
    catch (Exception exception) {
      exception.printStackTrace();
    }
  }
  /**
   * Retrieves a copy of a published object from the library
   * and restores it if necessary.
   *
   * @param stringName the name of the object
   *
   * @returns the object, or null
   *
   */
  public
  Object
  retrieve(String stringName) {
    Object object = null;
    try {
      // Use the properties to establish an initial directory context.
      DirContext dircontext = new InitialDirContext(m_properties);
      // Look up the object using the specified name.
      object = dircontext.lookup(stringName);
      // Close the context.
      dircontext.close();
    }
    catch (Exception exception) {
      exception.printStackTrace();
    }
    return object;
  }

The getReference() method demonstrates how to create a Reference instance that represents the current instance of a class. Instead of being stored as serialized data, classes that implement the Referenceable interface are stored indirectly, via Reference instances. Notice how, in the example below, the properties are transformed and stored as a series of bytes. Typically, enough of an object's state must be stored to create a working copy of the object when the object is looked up in a directory service.

  /**
   * Gets the reference for the library.
   *
   * @returns the reference
   *
   */
  public
  Reference
  getReference() {
    Reference reference = null;
    try {
      // Store the properties as an array of bytes.
      ByteArrayOutputStream bytearrayoutputstream = new 
ByteArrayOutputStream();
      m_properties.store(bytearrayoutputstream, null);
      // Create a reference to the library.
      reference = new Reference(
        JNDILibrary.class.getName(),
        new BinaryRefAddr("properties", bytearrayoutputstream.toByteArray()),
        JNDILibraryFactory.class.getName(),
        null
      );
    }
    catch (Exception exception) {
      exception.printStackTrace();
    }
    return reference;
  }

JNDILibraryFactory.java

The JNDILibraryFactory class is used on the client-side to create an instance of the JNDILibrary class when a JNDI library is looked up in a directory service. JNDILibraryFactory's primary operation: transform the stored binary property-file information into live property information. This transformation allows the new instance to function identically to the version that was stored:

  /**
   * Creates an object instance given a reference.
   *
   * @param object a reference
   * @param name a name
   * @param context the context
   * @param hashtable the environment
   *
   * @returns an object, or null
   *
   */
  public
  Object
  getObjectInstance(Object object, Name name, Context context, Hashtable 
hashtable)
  throws Exception {
    // Checks whether or not the object is an instance of a reference.
    if (object instanceof Reference) {
      Reference reference = (Reference)object;
      // Checks whether or not it is a valid reference for this factory.
      if (reference.getClassName().equals(JNDILibrary.class.getName())) {
        // Gets the stored payload.
        RefAddr refaddr = reference.get("properties");
        if (refaddr != null) {
          if (refaddr instanceof BinaryRefAddr) {
            BinaryRefAddr binaryrefaddr = (BinaryRefAddr)refaddr;
            // Recreates the stored properties.
            byte [] rgb = (byte [])binaryrefaddr.getContent();
            ByteArrayInputStream bytearrayinputstream = new 
ByteArrayInputStream(rgb);
            Properties properties = new Properties();
            properties.load(bytearrayinputstream);
            // Creates a new JNDI library with the stored properties.
            return new JNDILibrary(properties);
          }
        }
      }
    }
    return null;
  }
1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more