# Using JNDI - JSP

As mentioned earlier in this chapter, JNDI is a standard component of JDK 1.3 and higher, and as such, is also shipped as part of J2EE 1.2 and above.

While you are developing an application, you must ensure that the CLASSPATH contains the location of the JNDI libraries so that the Java compiler has access to them.This will be the case as long as the JAVA_HOME environment variable has been set to correctly point to the installation directory of a compatible JDK.

When you run a JNDI-aware application, whether it is a simple command-line client or a Web application, there must be a JNDI service running, and the classes for that service must be available to the program. Again, this means setting the CLASSPATH correctly, usually by placing one or more vendor-supplied JAR files on the CLASSPATH. For specifics, you should consult the documentation supplied by either the JNDI provider or J2EE server vendor. When you start a J2EE server, the default behavior is that a naming service is automatically started, too. If this default behavior is not required, for example, if you want to use an existing JNDI server, you need to change the J2EE server configuration appropriately. In this part of the chapter you will see a command-line example that uses Sun’s J2EE Reference Implementation (RI) to bind and look up an object. Later in the chapter you can find an example that uses BEA’s WebLogic J2EE server to publish an Enterprise JavaBean (EJB) that a JSP uses.

JNDI and Sun’s J2EE Reference Implementation

It is straightforward to set up your machine so that you can use JNDI with Sun’s J2EE RI. All you must do is ensure that

• The J2EE_HOME environment variable is set to the directory in which the J2EE SDK is installed.You can download the J2EE SDK from http://java.sun.com/products/j2ee.
• The CLASSPATH environment variable contains the j2ee.jar file that is in the lib directory under the J2EE home directory, for use by JNDI clients.

The way that I tend to set up my development machines is to set a system-wide environment variable for J2EE_HOME, and then have a command-line script that I can run from a command prompt to set up the CLASSPATH when I need it.This prevents unnecessary clutter, and means that I know exactly what is on the CLASSPATH at any given point! Setting the CLASSPATH as a system-wide variable can lead to all sorts of confusion when you have multiple JAR files from different vendors, especially when they ship different versions of the same JARs. The batch file on my Windows XP machine looks like this:

set path=%J2EE_HOME%in;%PATH% set classpath=%J2EE_HOME%libj2ee.jar;%CLASSPATH%;. Under Unix or Linux, you could use a line like this: CLASSPATH=$J2EE_HOME/lib/j2ee.jar:$CLASSPATH

To start the J2EE server, all you need to do is issue the following command at a command prompt: j2ee -verbose

The J2EE server runs until you either close its window, or issue the following command at another command prompt:j2ee -stop

Obtaining an Initial Context

The first thing you must do when you use a JNDI naming service is to obtain a context in which you can add and find names.The context that represents the entire namespace is known as the initial context.You need to have an initial context, since all of the operations that you can perform on naming and directory services are performed relative to a context.

In Java code, you represent the initial context with an instance of the javax. naming.InitialContext class. As mentioned earlier in this chapter, this class implements the javax.naming.Context interface that defines methods for examining and updating bindings within a naming service.

The way that you retrieve a reference to an initial context is very simple and can be performed with this line of code: Context initialContext = new InitialContext();

However, there are several things that can go wrong when this code is executed. In any of these cases, an instance of the javax.naming.NamingException class is thrown.The four most common errors and their reasons are as follows. First of all, you get the following exception if the JNDI server is not running or if the JNDI properties for the server are not set correctly: avax.naming.CommunicationException: Can’t find SerialContextProvider Second, if the InitialContext class has neither default properties for the JNDI service provider nor explicitly configured server properties, you will see the following exception:

javax.naming.NoInitialContextException:

➥Need to specify class name in environment or system property, or as an applet
➥parameter, or in an application resource file: java.naming.factory.initial

Third, if the classpath for the JNDI program does not contain the JNDI server classes, you see this exception: javax.naming.NoInitialContextException: Cannot instantiate class: XXX [Root exception is java.lang.ClassNotFoundException: XXX]Fourth, if the JNDI properties for the program do not match the JNDI Service Provider, you see this exception: javax.naming.ServiceUnavailableException:

➥ Connection refused: no further information

[Root exception is java.net.ConnectionException:

➥ Connection refused: no further information]

The next section,“Configuring the JNDI Service Provider,” details how to avoid all four of these exceptions.

Configuring the JNDI Service Provider

Although you are developing an application on your personal development machine, it is perfectly reasonable for you to use a JNDI service that is running on your local machine. For example, you could use the default service provider shipped with your J2EE server. However, when you deploy the application you need to use the naming service used by your organization.This means that you must configure your application to use a specific naming service rather than the one that is running on your personal development J2EE server. Some implementations from vendors might require additional parameters, but the core information that you need to provide to define a JNDI service is

• The server’s DNS host name
• The socket port number on the server
• The JNDI service class name

There are several ways to provide this information to an application, but all you need to do is choose one of these options:

• Add the properties to a JNDI Properties file in the Java runtime home directory.
• Provide an application resource file for the program.
• Set command-line parameters that are passed to the application.You can do this using the Java interpreter’s -D option.The downside to using this approach is that the command lines tend to become unwieldy, although you could always use a script that contains the properties that you set.
• In the case of an applet, you can specify parameters that are passed to it by using the <param> tags that can be nested in <applet> tags.
• Hard-code the values into the application.This is not a preferred approach because it restricts the application to the naming service on the host that you specify.

The use of hard-coded properties is the least desirable because you have to edit, recompile, and redeploy an application if you change the service provider or if the naming server moves to a different host. However, if you want to try it out, perhaps in a test environment, you simply set the properties using a hashtable, with code like this:

Notice that the code does not use the property names such as java.naming. factory.initial, but instead uses Context.INITIAL_CONTEXT_FACTORY and Context.PROVIDER_URL.This is because the javax.naming.Context interface defines a set of constants for the names of the properties that you need to set.Thus, you do not have to remember strings such as java.naming.factory.initial.This also makes your code more flexible because it is independent of any changes that might be made to the property names in future versions of JNDI.You will see more on the different properties and their names shortly. Although it is possible to hard code the JNDI properties, it is the first two approaches that are the most suitable for production environments. For both, all that you need to do is distribute a text file with the application. When you create an InitialContext, JNDI searches for any application resource files called jndi.properties on the classpath. JNDI also looks in the Java runtime home directory (which is the jre subdirectory in the Java JDK home directory) for a file called libjndi.properties.All the properties that you define in these files are placed into the environment that belongs to the initial context. For example, the j2ee.jar file in the lib directory of the J2EE RI contains these lines::

java.naming.factory.initial=com.sun.enterprise. naming.SerialInitContextFactory java.naming.factory.url.pkgs=com.sun.enterprise.naming

These are a set of properties, which are simply name/value pairs. In practice, as long as the j2ee.jar file is on the classpath, you should be all set.The first of these two properties, java.naming.factory.initial, enables you to set the fully qualified class name of the Initial Context Factory for the JNDI Service Provider.That is, you use this property to specify which JNDI Service Provider you want to use. If you want to use the default naming service supplied with the J2EE RI (and the j2ee.jar file is not on your classpath), you would use the following line in your jndi.properties file:

java.naming.factory.initial=com.sun.enterprise. naming.SerialInitContextFactory

Sun Microsystems provides several free reference implementations that are mentioned in the Table. You can specify the values from the table for the Context. INITIAL_ CONTEXT_FACTORY environment property.

Values of Context.INITIAL_CONTEXT_FACTORY (java.naming.factory.initial)

Value Naming Service

com.sun.jndi.cosnaming.CNCtxFactory CORBA Naming Service (COS) com.sun.jndi.fscontext.RefFSContextFactory File System com.sun.jndi.dnc.DnsContextFactory DNS com.sun.jndi.ldap.LdapCtxFactory LDAP com.sun.jndi.rmi.registry.RegistryContextFactory RMI Registry

You can find more information on these properties in the documentation for the javax.naming.Context and Sun’s JNDI Tutorial . How about if you need to access a JNDI service on a remote machine? If you needed to reference a JNDI service on a machine called nameserver.samspublishing.com on port 4242, you would set this property in the jndi.properties file:

java.naming.provider.url=nameserver.samspublishing.com:4242

The java.naming.provider.url property specifies the DNS host name and service port number of the machine that is running the JNDI service.This is the only property that the network administrator needs to modify, but JNDI uses port 1099 on the localhost by default, and most sites do not need to change this value.

Binding JNDI Objects

After you have obtained an initial context, you can use it to bind new objects into the naming service and look up objects that are bound to a name. In J2EE applications, when working with EJBs, for example, the main use of JNDI is to look up objects that have already been bound.The J2EE server usually performs the actual binding of objects. To bind an object to a name within a J2EE naming service, you use code similar to that shown in Listing 13.1.The code simply binds a java.lang.String object (Some_String) to the name sams/book.

Listing 13.1 Binding an Object to a Name (BindObject.java)

A couple of common errors can occur when you attempt to bind an object to a name. First, you must make sure that the object implements the Serializable interface so that the server can store a copy of the object. Second, the Context.bind() method fails if an object is already bound to the name that you specify. In this case, a subclass of NamingException is thrown:

Note that the code in Listing 13.1 does not set values for any of the properties mentioned earlier in this chapter for specifying the service provider.This is because this example uses the J2EE RI, and the j2ee.jar file contains a jndi.properties file that is identical to the one mentioned earlier.

Name Persistence

You might have noticed that if you run the program from Listing 13.1 twice in succession, you get an error of the form: An error occurred while binding the object (Some_String)to the name ‘sams/book’

However, if you restart the J2EE RI, then you do not get an error.This is due to the fact that the default naming service for the J2EE RI is a transient service.This means that any objects that are bound through configuration files in the SDK home directory are rebound when the server starts up, but any objects bound programmatically through the Context.bind() method are not.

Rebinding Objects

You have two options if you want to avoid the javax.naming.NameAlreadyBoundException mentioned earlier.The first is to unbind an existing object, and then bind the new one.The next section,“Unbinding Objects,” describes this process. The second option is to do what the NameAlreadyBoundExcpetion recommends:You can use the Context.rebind() method that unbinds the old object and binds the new one for you. In this case, the code from Listing 13.1 would use this method call rather than a call to bind(): initialContext.rebind(TITLE, object);

Unbinding Objects

The Context.unbind() method removes an object from a namespace. Generally this method is used when an application is closing down and you want to remove the object from the naming service so that other applications do not attempt to make use of the bound object.This is necessary because bindings are not automatically removed when an application that uses a naming service is shut down. Another time that you commonly use the unbind() method is when you want to bind an object into a naming service under a name, but you first want to see if there is already an object bound under the name you are going to use.The advantage of using a combination of unbind()/bind() over just a call to rebind() is that you can add logic to see whether you should perform the bind() operation. For example, you might only want to bind the new object if it is of the same type (or a subclass) of the existing bound type:

There are three things that could happen with this code fragment when the lookup() method is invoked:

n The lookup fails because there is no object bound to the name sams/book, in which case the NameNotFoundException is ignored and the new object is bound in under the name sams/book.

n The lookup returns an object that is not of type java.lang.String, so the unbind() operation is not performed. In this case, the bind() method would throw a javax.naming.NameAlreadyBoundException.

n The lookup returns an object that is of type java.lang.String, and so the existing object is unbound and the new string is bound in its place.

Renaming Objects

It is a simple matter to change the name under which an object is already bound into the naming service.You simply use the Context.rename() method, which takes the old and new names as parameters:

initialContext.rename(“the_old_name”, “the_new_name”);

There are only two things to be aware of; the first is that the new name must be in the same context as the old name.The second is that the old name must be bound to an object, and the new name must not. A javax.naming.NamingException is thrown if these conditions are not met.

JNDI Name Lookup

By far the most common use of JNDI in Web applications is to look up objects that have been bound to names.To perform the lookup, you need two pieces of information:

• The JNDI name that the object is bound to
• The class of the bound object

After you know this information, all you need to do is use the Context.lookup() method to return a reference to the object, and then cast the reference to the correct type.The code in Listing 13.2 shows how to retrieve a bound object from a naming service. If you are running the code samples as you work through this chapter, make sure that you run the code in Listing 13.1 first.Then again, you might prefer not to so that you can see the code in the catch block executed.

Listing 13.2 Code That Looks Up a Bound Object (LookupObject.java)

The if statement is simply a sanity check because the code from Listing 13.1 binds a java.lang.String object under the name sams/book.The code in the else statement is executed if the object returned from the lookup() method is not a String.

Contexts

Contexts provide a hierarchical structure to JNDI names, and composite names group together related items.You have no doubt seen this same idea when using a file system where you might have a /usr directory for all the home directories of the users of a machine, such as /usr/mark and /usr/ruth.

The name used in Listings 14.1 and 14.2 (sams/book) is an example of a composite name. If you are looking up just one or two objects, you might want to use the full name each time. However, if you are looking up objects that are in the same context of a composite name, it is simpler to change to a subcontext and look up a simple name within that context. For example, if you wanted to look up two objects, sams/book and sams/book_cd, you can change to the sams context and perform two lookups, one for book and one for book_cd. The subcontext is a name entry in the same way as any other name, and you look it up in exactly the same way as before.The return type of the object passed back from the lookup() method is javax.naming.Context.The code in Listing 13.3 is similar to Listing 13.2, and retrieves a name from a subcontext. Note that this example assumes that you have already run the code in Listing 13.1.

Listing 13.3 Looking Up Objects from a Subcontext

For a given context you can also programmatically:

• List its subcontexts by invoking its listBindings() method.
• Create new subcontexts through its createSubcontext() method.
• Destroy subcontexts with its destroySubcontext() method.