The Doc-u-matic, a JNDI Application

At this point in the course, you should feel comfortable with naming and directory services and the operations they support. We will be working with a document publication and distribution application called Doc-u-Matic that illustrates JNDI's support for the following:

  • Centralized information administration

  • Network-wide information distribution

  • Object persistence

Doc-u-Matic is first-and-foremost a demonstration of JNDI-supported object persistence. Let's first start with a review of JNDI's support for stored objects.

JNDI's Support for Stored Objects

You may recall 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 we are going to look at uses two methods of object storage.

Building Blocks

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

Functional View of Doc-u-Matic.

Figure 4: Functional View of Doc-u-Matic.

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 is worth noting that nothing in the design of a library implies that it has to be implemented on top of 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.

Preparation

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

Before you proceed, make sure you have obtained, installed, and configured the following:

  • An LDAP implementation

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

  • Create the initial context that the application will connect to. The following is assumed:

Ou=HowTo,o=JavaWorld

Proper Usage

Before we jump 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 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.

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. Take a look at the following properties file:


# 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 will assume that you have already setup the classpath to point to the jndi.jar and ldap.jar JAR files):

Java JNDIDeploy <properties file>

Once you, as administrator, have deployed the library, you must distribute a properties file to all users. The client applications all read a properties file pointed to by a URL. As a result, the easiest way to distribute the 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. There are a few stipulations 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>

User's Role

With the user's role it is time to retrieve some of the objects that have been published via the administrator. 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 will see a little bit later, the code required to use a library is very small indeed.

There is a small client program that is designed to retrieve 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.

Code

Figure 5, below, shows the six classes that form the core of Doc-u-matic. We will discuss each briefly and then present important parts of the code.

Doc-u-Matic's Six Core Classes.

Figure 5: Doc-u-Matic's Six Core Classes.

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

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 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();
Basicattrobutes.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 binaryreaddr =
(BinaryRefAddr)refaddr;
// Recreates the stored properties.
Byte[] rgb = (byte []) binaryrefaddr.getContebt();
ByteArrayInputStream bytearrayinputstream =
New ByteArrayInputStream(rgb);
Properties properties = new Properties();
properties.load(bytearrayinputstream);
// Creates a new JNDI library with the
// stored properties.
}
}
}
}
return null;
}

The getObjectInstance() and the getReference() methods, provided by the JNDILibrary class, work as a pair.

JNDIDeploy.java

JNDIDeploy class deploys or creates a new library. The body implements as close to a textbook example of binding via JNDI as you are likely to find:


// Deploys a JNDILibrary instance.
//
// This class should be run as an application. It deploys a
// JNDILibrary instance. It assumes the existence of a
// properly configured JNDI service (typically, but not necessarily,
// running LDAP).
//
// The application requires a single command-line argument:
// the name of a file containing the deployment properties. The
// properties file may contain any valid JNDI property, as well as the
// property "library.name", which must contain the name of the
// library.
//
public static void main (String[] rgstring)
{
if (rgstring.length != 1)
{
System.out.println("Usage: java JNDIDeploy ");
System.exit(-1);
}
try
{
Properties properties = new properties();
// Load the properties from the specified file.
properties.load(new FileInputStream(rgstring[0]));
// Use the properties to establish an initial directory context.
DirContext dircontext = new InitialDirContext(properties);
// Create the JNDI library instance and initialize it with the
// same set of properties (it will publish objects in the same
// context as itself).
JNDILibrary jndilibrary = new JNDILibrary(properties);
// Get the name of the library.
String stringName = properties.getProperty("library.name");
// Bind the library to the specified name.
dircontext.rebind(stringName, jndilibrary, null);
// Close the context.
dircontext.close();
}
catch (Exception exception)
{
exception.printStackTrace();
System.exit(-1);
}
}

Publish.java

The Publish class publishes one or more objects in a library. Once again, you are not likely to find a more textbook example of how to look up an object via JNDI - in this case the library. Once you have retrieved the library, it can be used to push objects. Notice how the code doesn't have to know anything about how the library is implemented. You'll also notice that the Beans.instantiate() method instantiates the objects, which facilitates the storage of objects that have been put together in the JavaBeans development tool and serialized to disk:


// Publishes an object.
//
pubic static void main(String[] rgstring)
{
if(rgstring.length < 1)
{
System.out.println("Usage: java Publish *lt;URL> [name class]...");
System.exit(-1);
}
try
{
Properties properties = new Properies();

// Load the properties from the specified URL.
Properties.load(new URL(rgstring[0].openStream());

// Use the properties to establish an initial directory context.
DirContext dircontext = new InitialDirContext(properties);

// Get the name of the library.
String stringaName = properties.getProperty("library.name");

// Look up a library using the specified name.
Library library = (Library) dircontext.lookup(stringName);

// Close the context.
Dircontext.close();

// For each command-line argument, instantiate the specified
// object, and publish it to the library.
for (int i = 1; i < rgstring.length; i++)
{
Object object = Beans.instantiate(null, rgstring[i + 1]);
Library.publish(object, rgstring[i], new HashMap());
i++;
}
}
catch (Exception exception)
{
exception.printStackTrace();
System.exit(-1);
}
}

Client.java

The Client class is almost identical to the Publish class, except that it retrieves rather than publishes them.


// A Simple Client
//
// Demonstrates how to connect to and retrieve from a Library instance via JNDI.
//
public static void main(String[], rgstring)
{
if(rgstring.length < 1)
{
System.out.println("Usage: java Client <URL> [name] ...");
System.exit(-1);
}
try
{
Properties properties = new Properties();

// Load the properties from the specified URL.
properties.load(new URL(rgstring[0].openStream());

// Use the properties to establish an initial directory context.
DirContext dircontext = new InitialDirContext9properties);

// Get the name of the library.
String stringName = properties.getProperty("library.name");

// Look up a library using the specified name.
Library library = (Library)dircontext.lookup(stringName);

// Close the context.
Dircontext.close();

// For each command-line argument, retrieve the specified object
// from the library.
for (int i = 1; i < rgstring.length; i++)
{
Object object = library.retrieve(rgstring[i]);
// If the object is restoreable, restore it.
if(object instanceof Restorable)
{
Restorable restorable = (Restoreable)object;
Restoreable.restore();
}
}
}
catch (Exception exception)
{
exception.printStackTrace();
System.exit(-1);
}
}

Doc-u-Matic Tutorial

After going through this short tutorial and sample JNDI application, you should have a much clearer understanding of naming and directory services, JNDI, its capabilities, and the flexibility it brings to JNDI-enabled enterprise applications. JNDI already plays an important role in several key Java APIs, but this is beyond the scope of this course. You can bet one thing though - you can expect JNDI's role to continually expand.

Previous blank Table of Contents blank Next