www.developer.com/java/ent/article.php/2215571
Locating Resources
Using JNDI (Java Naming and Directory Interface) By Sams Publishing June 2, 2003 This material is from Chapter 13, Locating Resources Using
JNDI, from the book JavaServer Pages Developer's Handbook (ISBN:
0-672-32438-5) written by Nick Todd and Mark Szolkowski, published by Sams Publishing. Chapter 13: Locating Resources Using JNDIThis chapter introduces the concepts surrounding the Java Naming and Directory Interface (JNDI). It discusses the need for naming services, and the purposes for which Web applications use them. Directory services are also described, and by the time you have read this chapter you will be able to distinguish between the two types of service. You will then be introduced to JNDI and its architecture before seeing the specifics of using JNDI in a Web application. In the next chapter (Chapter 14, "Databases and JSP") there are examples of using JNDI to locate JDBC datasources. In Chapter 15, "JSP and EJB Interaction," you will see Web applications that use JNDI to locate Enterprise JavaBeans (EJBs).
Naming and Directory ServicesThis part of the chapter discusses naming services, and then directory services. After you have read them you will know what each is, as well as the differences between them. You have probably already come across several such services, such as DNS and NDS. Overview of Naming ServicesA naming service is quite simply a software application that associates a name with the location of information or services. This means that the software you write can utilize objects without any knowledge of where those objects are located. The objects need not even reside on your local machine, but can live on any machine that is accessible on the network. Another benefit to using a naming service is that for most people it is much easier to remember a logical name rather than a URL or some other object reference. For example, you can associate a logical name with a JDBC datasource. It is much easier to remember a name like CUSTOMER_ADDRESSES than a JDBC URL such as jdbc:mysql://localhost:3306/ADDRESS! This really is not that much different from many examples in day-to-day life. For example, if you want to make a telephone call to somebody whose number you don't know, you normally look that number up in a telephone book. Conversely, you can register your own telephone number with the producers of the telephone book so that other people can look you up. The only tricky part about looking up somebody's number in a telephone book (assuming that they are listed) is making sure that you are looking in the correct telephone book. You have a similar problem to overcome when writing computer software that uses a naming service, in that you can only lookup an object if you search the correct naming service. The term given to this is that you must obtain a context. When you then use a context to retrieve information from a naming service, you are said to perform a lookup. The act of storing the name/resource pair in the naming service in the first place is known as binding. However, when people use the term a binding, they are referring to the association between an object and its name. After an object has been registered by name in the naming service, a client can retrieve a reference to the object by specifying the same name. Figure 13.1 shows the basic architecture involved with using a naming service. The diagram depicts a client that retrieves an object by specifying a name that was previously used to bind an object into the naming service. You can see that the naming service associates a name with an object (a binding). Figure 13.1 You have just read that a context is a set of name/resource pairs. A naming system contains a set of related contexts that have the same naming convention. It is this naming system that provides the naming service to clients. The set of names in a naming system is known as a namespace. Several common naming services are
One example of a binding is a file that is bound to its filename. Another is an IP address that is bound to a hostname in DNS or WINS. At the very least, a naming service must provide the capability to bind objects to a name and support the retrieval of those objects by name. However, the way in which the naming service can store the objects can differ. For example, the actual resource might be stored inside or outside the naming service. A naming service that does not store the resource directly is DNS. DNS simply maps a logical name such as http://www.samspublishing.com/ to an IP address (165.193.123.117), but does not store the remote host itself. This situation also arises when the object that is associated with the name is large, and you do not want to store it in the naming service. In this case you can store a reference to the object instead. An example of a naming service that can store objects internally is the file system provided by Microsoft Windows NT. For efficiency, NTFS stores files that are smaller than about 1KB in the Master File Table (MFT). Anything larger than this is stored externally. It is possible to overwrite an existing binding by specifying the same name, but a different resource. This is known as rebinding. In the previous telephone number example, this is analogous to moving and being allocated a new number by the telephone company. Other things that you can do with a naming service include renaming a bound object, and unbinding it completely so that it is no longer available to clients.JNDI also supports the notion of federated namespaces. This is when a resource is identified by a name that spans multiple naming systems. For example, consider the name myhost.somedomain.com/documents/manual.txt. The first part of this name (myhost.somedomain.com) is a host name that must be resolved via DNS, and the rest of the name (documents/manual.txt) is a file system name. For details of how this works, see the JNDI tutorial at http://java.sun.com/products/jndi/tutorial/beyond/fed/index.html. Overview of Directory ServicesA directory service is similar to a naming service in that it enables you to associate names with objects. However, a major distinction between the two is that a directory service enables you to store information about the object (these pieces of information are known as attributes), in addition to providing mechanisms for searching for objects based on that information. For example, if you need to print out a color photograph, you could use a directory service to find the locations of color printers in your office building. See Figure 13.2 for a diagram of a generic directory service. Figure 13.2 Going back to the real-world telephone book example, using a directory service is similar to using the Yellow Pages phone directory. Instead of simply listing the name of a business along with a contact telephone number, the Yellow Pages directory often includes advertisements that contain additional information that add value to the entry. For example, a business might list location maps, professional qualifications, and even affiliated organizations. The fact that a directory service enables you to search for objects based on the values of these attributes means that you can, for example, search for all plumbers who operate a 24-hour emergency service in your neighborhood. |
A popular protocol for accessing directory services is the Lightweight Directory and Access Protocol (LDAP). LDAP is a protocol that defines how client applications can manipulate data on a directory server, but says nothing about how the data should be stored. Generally speaking though, directory services usually allow you to store objects in a hierarchical fashion. LDAP servers, for example, arrange all objects in a tree known as the Directory Information Tree (DIT). The categorization of entries can simplify the search for particular objects. For example, a Yellow Pages directory might have categories for lawyers and carpet fitters. The categorized entries are a form of subcontext within the directory context of the Yellow Pages directory.
In essence, a directory service is really just a simple database that enables you to search for data, and to narrow that search by specifying search criteria. When you perform a search of a directory service, there are three pieces of information that you need to specify:
A search filter with the values of the attributes. For example, you could search for all employees whose gender attribute has the value male.
A search base that identifies the node in the tree that you want the search to begin from.
A scope that limits the depth of the tree that is searched.
Relational Database Management Systems (RDBMS) is another technology that might spring to mind while reading this discussion of directory services. An RDBMS enables you to create, update, retrieve, and remove entries that are stored within it, as does a directory service. One difference is that the internal data tends to be stored differently. This is because an RDBMS generally uses a relational information model that involves the use of tables. An RDBMS usually also supports transactions that allow a group of operations to be rolled back if a particular step fails for some reason. Directory services are designed more to be very quick at reading and searching for data, and use a hierarchical data model as mentioned earlier.
Note that the syntax for specifying names varies between naming services. For example, the Microsoft Windows operating system allows you to use the \ character to separate the components in a path such as c:\winnt\system32\drivers\etc\hosts. When you use the DNS naming convention, you specify a name, such as http://www.samspublishing.com/, where each component in the path is separated by the . character.
LDAP servers use names that are based on the X.500 standard. Such names (known as distinguished names) have the following general form:
cn=Peter Szolkowski, ou=Bikers, o=MANX_RACERS, c=uk
This form might also be familiar to you if you have used Microsoft's Active Directory service, which also uses names based on the X.500 standard. The difference is that it uses the / character rather than commas to delimit the name components:
cn=Peter Szolkowski/ou=Bikers/o=MANX_RACERS/c=uk
Both LDAP and Active Directory use hierarchical names. When the names are read from left to right, the most specific part of the name occurs first and the least specific part occurs last.
Tip - Some JNDI naming service implementations (also known as JNDI Service Providers) use case-sensitive names, and some do not. However, to maintain the portability of your applications you should avoid names that only differ by case. It is also a good idea to make sure that names are always spelled in a consistent fashion.
When you use JNDI, most of the time you simply specify a string that JNDI passes on to the underlying naming service with a minimum of interpretation. You should be aware that JNDI also provides support for creating and manipulating structured names.
One reason to use a naming service is that it enables you to decouple the provider of a service from its consumer. This is because the name that the supplier of the service uses to register the service is the only thing that the consumer needs to know.
Another reason to use a naming service is that it can provide an application with a single repository of information in which it can find all of its required resources.
When you use a naming service, you have a consistent way of publishing services that is independent of any particular platform and, therefore, is portable. In addition, you are free to migrate a service from one host to another. All that you would need to do is update the entry in the naming service to point to the new location of the service. The beauty of this is that the client needs to know nothing about the fact that the service has moved.
If you did not use JNDI to access a naming service, life would be a lot more difficult when you have to provide services such as those that are implemented using J2EE objects such as message queues, EJBs, and data sources. Every vendor would have to implement a proprietary mechanism that defined how client code gains access to J2EE objects. For example, one vendor might use TCP/IP broadcast network packets, whereas another could use textual configuration files.
JNDI is a Java API that has been available since J2SE 1.2, and is also a part of J2EE 1.3.1. If you are using older versions of the SDKs, you should be aware that JNDI is also included in J2EE 1.2 and is available as a standard Java extension for JDK 1.2 and earlier releases.
The JNDI API defines an interface that Java programs can use to access a variety of naming and directory services in a uniform way. JNDI was designed specifically for the Java platform, and uses Java's object model. Therefore, you can use JNDI to store and retrieve Java objects of any type.
It is perhaps helpful to also state what JNDI is not: It is not a naming and directory service implementation, only an API. Thus, to use JNDI, you must also have available an implementation of a naming and directory service.
Without JNDI, it is necessary to learn the specific APIs that are implemented by the naming and directory service that you are using. This makes life a lot more difficult for application developers because they need to know all the APIs for the different naming and directory services used in their enterprise, thus leading to harder-to-maintain code. Figure 13.3 shows the architecture of a client and multiple services that each provides its own API.
Figure 13.3
The architecture of a system that does not use
JNDI.
In fact, JNDI consists of both an Application Programmer's Interface (API) and a Service Provider's Interface (SPI). Figure 13.4 shows the architecture of how your application, the JNDI API, SPI, and the naming service implementations fit together. Because the JNDI API is defined in a way that is independent of any individual directory service implementation, it is possible to use additional naming services as long as they implement the SPI for JNDI. A service provider is basically a driver that your application can use to communicate with a directory service.
Figure 13.4
The JNDI architecture.
The JNDI architecture's layered design was constructed to help insulate client code from naming service provider code.
The JNDI classes and interfaces are divided into five main packages: javax.naming, javax.naming.directory, javax.naming.event, javax.naming.ldap, and javax.naming.spi. These packages are covered in the next five subsections of this chapter.
Note - You might be wondering why JNDI is so large. If all that you ever want to do is look up J2EE objects, Sun could have made JNDI as simple as a name to an object mapping service. Most of the time, this is exactly what you want. However, Sun designed JNDI to interoperate with many existing naming and directory services so that it would not be used as a proprietary product with J2EE servers.
The javax.naming package contains the classes and interfaces that your application can use to access naming services. The Context and Name interfaces are part of this package, as well as the Reference and Binding classes.
A binding is a set of information that contains an object's name, the name of the class used to instantiate the object as well as the actual object itself.
Within a naming service, a set of bindings is referred to as a context. The javax. naming.Context interface is the principal interface in JNDI because it defines methods that enable you to
Bind objects to, and unbind objects from, names.
Rename objects.
Retrieve objects with the lookup method.
You can also use the list and listBindings methods to retrieve an enumeration of name-to-object bindings. The listBindings method returns an enumeration of type javax.naming.NamingEnumeration, where each element in the enumeration is of type javax.naming.Binding, as described later.
The list method is more lightweight, in that it also returns an enumeration, but this time each element is of type javax.naming.NameClassPair. An instance of this class contains an object's name and the name of the class that was used to instantiate the object. The list method is useful when an application needs information about the object, but not the object itself. For example, you might be writing some kind of browser that displays a list of objects in the naming service.
This interface symbolizes a generic name of an object that is bound into a naming service. There can be many different implementations of a name, such as URLs or host names, but the Name interface provides methods for accessing the name that are independent of the underlying naming service. A name typically consists of a string or a group of name components.
In general, applications that need to manipulate individual components in a name would use the Name interface to build a name or compare with another name, for instance. Simple applications generally use a java.lang.String to perform a lookup operation.
Depending on the naming and directory service that you use, you might or might not be able to store a serialized Java object directly within the service. Even if your service is capable of storing the object, you might decide not to do so. For example, the object might be very large, or there could be applications written in languages other than Java that access the object in which case a serialized Java object would be of no use.
JNDI defines the javax.naming.Reference class that represents a reference to an object. A reference contains information that enables you to access an object. JNDI maintains the illusion that what the client looks up in the naming service (a reference) is in fact an object.
The javax.naming.directory package contains classes and interfaces that you can use to access directory services. For example, you can retrieve the attributes that are associated with an object. You can also perform searches for objects whose attributes match certain search criteria that you specify.
The two most important interfaces in the javax.naming.directory package are Attribute and DirContext.
This interface represents an attribute of a named object in the directory service. The actual forms that an attribute's name and value can take are dictated by the directory service. Some directory services allow you to specify a schema that sets the forms for the name and value.
An attribute has zero or many values associated with it. It is perfectly legal for a particular attribute value to be null. You can use the get and getAll methods to obtain the attribute values; the set method allows you to set a value at a specified index; and the remove method deletes an attribute at a specified index.
This interface represents a directory context, and defines methods that enable you to write software that examines (getAttributes) and updates (modifyAttributes) the attributes of a named object in the directory service.
There is also a set of overloaded search methods that enable you to search the directory service based on the name of a context (or object) and attribute values.
The DirContext interface extends the javax.naming.Context interface, and thus you can also use it as a naming context. This means that any object in the directory service can act as a naming context. For example, there could be an object in the directory service that represents an employee in your company. The employee object can have attributes associated with it as well as act as a naming context so that you could locate objects that belong to the employee such as their PCs, mobile telephones, and PDAs.
The javax.naming.event package defines classes and interfaces that support event notification mechanisms in naming and directory services.
If you have used the Java Event Model (as used in GUIs and by JavaBeans) that has been available since JDK 1.1, the mechanism described here will sound familiar. The basic idea is that an event source generates events that are sent to registered event listeners. The event mechanism is of an asynchronous nature, and means that applications can register an interest in changes to the directory service without having to poll the directory service for changes.
The NamingEvent class and the NamingListener interface described later are part of the javax.naming.event package.
The javax.naming.event.NamingEvent class represents an event object that is generated when something changes in a naming or directory service. The object contains information about the event that occurred, such as the source of the event, as well as a type that indicates the form that the event took. Events are classified into those that affect the namespace, and those that do not. An example of the former category would be when an object is added, whereas an example of the latter category is when an object is changed.
Other information about the change, such as information before and after the change, is also stored in the NamingEvent object.
An event source creates an instance of the NamingEvent class, and passes it to the registered listeners who can then use the instance methods of the object to extract information about the event.
You can implement the javax.naming.event.NamingListener interface to listen for NamingEvents. However, there are several subinterfaces of NamingListener that correspond to the different categories of event that can occur.
For example, there is a NamespaceChangeListener for events that change the namespace, such as the addition, removal, or renaming of an object. There is also the ObjectChangeListener for notification of modifications to objects in the namespace, which covers when an object's binding is replaced with another and when an object's attributes are replaced or removed.
Typically, you implement a subinterface rather than directly implement NamingListener.
You can find classes and interfaces in the javax.naming.ldap package that enable you to access features specific to LDAP v3 that are not already covered by the classes and interfaces in the javax.naming.directory package.
Most JNDI applications will not need to use the javax.naming.ldap package. The only time that you will is if you are writing software that needs access to LDAP functions such as Controls, Extended Operations, and Unsolicited Notifications. You can find more information about these in the LDAP RFC at http://www.ietf.org/&rfc/rfc2251.txt.
The classes and interfaces defined in the javax.naming.spi package are primarily for use by developers of naming/directory service providers.
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. If you want to run JNDI applications under JDK 1.2 then you can download a standard extension from Sun's Web site at http://java.sun.com/products/jndi.
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.
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%\bin;%PATH% set classpath=%J2EE_HOME%\lib\j2ee.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
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:
javax.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 appletparameter, 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.
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:
Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.enterprise.naming.SerialInitContextFactory"); env.put(Context.PROVIDER_URL, "localhost:1099"); Context initialContext = new InitialContext(env);
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 lib\jndi.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 14.1. You can specify the values from the table for the Context.INITIAL_CONTEXT_FACTORY environment property. Sun Microsystems maintains a list of service providers for JNDI on its Web site at http://java.sun.com/products/jndi/serviceproviders.html.
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 (http://java.sun.com/products/jndi/tutorial/index.html).
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.
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.
package com.conygre.jspdevhandbook.chapter13; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class BindObject { public static final String TITLE = "sams/book"; public static void main (String[] args) { Object object = "Some_String"; try { Context initialContext = new InitialContext(); initialContext.bind(TITLE, object); System.out.println("Bound object to name: " + TITLE); } catch (NamingException e) { System.err.println("An error occurred while binding the object (" + object + ") to the name '" + TITLE + "'"); e.printStackTrace(); } } }
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: NameAlreadyBoundException.
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.
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' javax.naming.NameAlreadyBoundException: Use rebind to override
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.
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);
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:
String JNDI_NAME = "sams/book"; try { Object o = initialContext.lookup(JNDI_NAME); if (o instanceof String) initialContext.unbind(JNDI_NAME); } catch (NameNotFoundException e) { // ignore: means that the lookup failed, so there is no existing object } initialContext.bind(JNDI_NAME, "some other string");
There are three things that could happen with this code fragment when the lookup() method is invoked:
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.
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.
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.
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.
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.
package com.conygre.jspdevhandbook.chapter13; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class LookupObject { public static void main (String[] args) { try { Context initialContext = new InitialContext(); Object object = initialContext.lookup(BindObject.TITLE); if ( object instanceof java.lang.String) { String s = (String) object; System.out.println("Looked up this object: " + s); } else { System.err.println("Error: The looked up object is not of type " + "java.lang.String"); } } catch (NamingException e) { System.err.println("Couldn't find an object bound to the name " + BindObject.TITLE); } } }
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 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.
package com.conygre.jspdevhandbook.chapter13; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; public class LookupObjectSubContext { public static void main (String[] args) { try { Context initialContext = new InitialContext(); Context samsContext = (Context) initialContext.lookup("sams"); String stringObject = (String) samsContext.lookup("book"); System.out.println("Looked up: " + stringObject); } catch (NamingException e) { System.err.println("Couldn't find an object bound to the name " + BindObject.TITLE); } } }
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.
To give you a taste of things to come in later chapters, here is a simple JSP that obtains a reference to an EJB through JNDI. This example does not run through how to deploy the example because this is detailed in Chapter 17. However, if you are familiar with deploying an Enterprise Archive (an .ear file) in an application server, or if you have read Chapter 17 already, to run the example you will need the chapter13.ear file from the book Web site.
What you will see here are screenshots of the example running in the WebLogic application server from BEA, but of course you can run it equally well under Sun's free RI server if you do not have an application server available.
The code in Listing 13.4 is for the EJB in this example. Don't worry too much about most of the code for now. For the purposes of this chapter, the important thing to realize is that the EJB implements a getHello() method that returns the nauseatingly familiar string Hello World!.
package com.conygre.jspdevhandbook.chapter13; import java.rmi.RemoteException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; public class HelloWorldBean implements SessionBean { public String getHello() { return "Hello World!"; } public void ejbCreate() {} public void ejbRemove() {} public void ejbActivate() {} public void ejbPassivate() {} public void setSessionContext(SessionContext sc) {} }
The JSP in Listing 13.5 uses a simple scriptlet to lookup a reference to the EJB, which in this example is bound to the name MyHelloWorld.
Note - The use of scriptlets in JSPs is now discouraged. However, this example uses one to keep things simple. In practice, you would use a JavaBean rather than a scriptlet.
<%-- The EJB's remote interface is "HelloWorld"; the JNDI name is "MyHelloWorld" Access this page through http://localhost:8000/HelloJNDI/Hello or http://localhost:8000/HelloJNDI/HelloWorld.jsp --%> <%@ page import="javax.naming.Context" %> <%@ page import="javax.naming.InitialContext" %> <%@ page import="javax.rmi.PortableRemoteObject"%> <%@ page import="com.conygre.jspdevhandbook.chapter13.HelloWorld" %> <%@ page import="com.conygre.jspdevhandbook.chapter13.HelloWorldHome" %> <html> <head><title>HelloWorld, JNDI</title></head> <body> The EJB says, <% Context initCtx = new InitialContext(); Object o = initCtx.lookup("MyHelloWorld"); HelloWorldHome home = (HelloWorldHome) PortableRemoteObject.narrow(o, HelloWorldHome.class); HelloWorld hello = home.create(); String theGreeting = hello.getHello(); out.println(theGreeting); %> </body> </html>
You can see from Listing 13.5 that the code to look up the EJB is practically the same as the examples you saw in Listing 13.2 and Listing 13.3:
Context initialContext = new InitialContext(); Object o = initialContext.lookup("MyHelloWorld");
The only difference is that when you look up an EJB, you must first look up something called the home interface, and then use that to create an instance of the EJB. When you get hold of the home interface, you must use the PortableRemoteObject.narrow() method rather than a simple programmatic cast. The reasons behind this are explained in Chapter 17.
After you have the EJB, invoke its methods as you would for any other Java object:
String theGreeting = hello.getHello();
When executed using the URL http://localhost:7001/HelloJNDI/HelloWorld.jsp, the output should be along the lines of what is shown in Figure 13.5.
Figure 13.5
The output from the HelloWorld JSP
(HelloWorld.jsp).
You might be wondering how the mapping between the EJB and its name of MyHelloWorld is set up. Within the chapter13.ear file there is a JAR file called ejb-jar-ic.jar that contains an XML document called weblogic-ejb-jar.xml. This latest XML file is specific to the WebLogic application server and maps the EJB (HelloWorldBean) to its JNDI name (MyHelloWorld):
<?xml version="1.0"?> <!DOCTYPE weblogic-ejb-jar PUBLIC "-//BEA Systems, Inc.//DTD WebLogic 7.0.0 EJB//EN" "http://www.bea.com/servers/wls700/dtd/weblogic-ejb-jar.dtd"> <weblogic-ejb-jar> <weblogic-enterprise-bean> <ejb-name>HelloWorldBean</ejb-name> <jndi-name>MyHelloWorld</jndi-name> </weblogic-enterprise-bean> </weblogic-ejb-jar>
When the application is deployed in WebLogic, you can use the WebLogic Server Console to view this information as in Figure 13.6.
Figure 13.6
The WebLogic server console.
A naming service provides a means of storing simple information under a name, so that by specifying the registered name you can retrieve the information. A directory service is similar to a naming service except that it additionally allows attributes to be associated with the stored objects. Directory services use the attributes to categorize names, which means that powerful searching of the directory tree structure can be supported.
JNDI provides a uniform API into a naming or directory service. You can use any naming or directory service through JNDI as long as you have access to a Service Provider implementation for that service.
It is simple to use JNDI from any Java program, including Web applications.
The most common uses of JNDI in Web applications are to publish objects such as
Databases (called data sources). You will see this in Chapter 14, "Databases and JSP."
Enterprise JavaBeans (EJBs). See Chapter 15, "JSP and EJB interaction," for details.
JMS message queues and topics.
The authors of this book work for Content Master Ltd., a technical authoring company in the United Kingdom specializing in the production of training and educational materials. For more information on Content Master, please see its Web site at www.contentmaster.com.
Nick Todd B.Sc. PGCE has been with Content Master since 2000 and has been working in technical education since 1997. Prior to that, he was a Web designer, the lead developer on a Web site for a British university, and he also worked on a Web site for British Telecom. Nick has wide experience in providing training and consulting in Java, XML, and Internet technology to companies such as Sun Microsystems, Art Technology Group, Stilo Technology, and the UK eScience Institute based at Edinburgh University. In conjunction with Content Master, Nick has also written courses on Java technology for the Sun Microsystems global curriculum and also courseware on Web-related technology for Microsoft. When Nick is not sitting in front of a computer or standing in front of a technical class, you will often find him working in the Christian church that he leads in Bristol, England.
Mark Szolkowski, M.Eng was born in Manchester, England in 1972, and has a Masters degree in Microelectronic Systems Engineering. During his four years at UMIST, he spent the summers learning the wonders of writing 16-bit Windows software in C using nothing more than M and a command-line compiler. After leaving university, Mark worked as a System Administrator at a software house, which allowed him to feed his programming habit. Since 2000, Mark has mostly worked as an independent technical trainer and author, specializing in Java and XML technologies. Mark has been working with Content Master since 2001.
![]() |
This material is from Chapter 13, Locating Resources Using JNDI, from the book JavaServer Pages Developer's Handbook (ISBN: 0-672-32438-5) written by Nick Todd and Mark Szolkowski, published by Sams Publishing. . |
To access the full Table of Contents for the book