Monday, 7 March 2011

J2ee: Java Naming and Directory Interface (JNDI)

Naming and Directory Service

There was a glimpse of Naming Service in RMI. JBS told you that objects that have to be looked up by clients in a distributed system are registered in a naming service and in a naming service there is a “textual name” to “object reference” mapping. A naming service can thus be described as a feature that helps to associate a name with an object and thereby provides an interface for clients to access the object.

Like RMI other naming services are DNS (Domain Name System), which maps a textual name of a site (as, sun.java.com) to a machine’s IP Address on network. File systems also offer naming service, which maps a file name to the actual file.

A directory service is similar to a naming service, except that in addition to associating a name with the object, it also associates attributes with the object. For instance, if only authenticated users are allowed access to an object stored in a directory service, then corresponding user names and passwords are saved with objects as attributes. Access to an object is granted depending on its authentication against its user name and password.

An object in a directory service can be searched based on it’s and attributes. A directory service is arranged in a hierarchical tree-like structure, whereas a naming service is not. Thus, an organization can arrange its data better in a directory service. Every directory service is a naming service whereas every naming service need not be a directory service. The RMI registry is only a naming service and not a directory service, whereas a file system is a directory service offering naming service as well. Some of the popular directory services available in the market are Novell Directory Service (NDS) and Microsoft’s Active Directory Service.

Naming and Directory service Concepts

JBS telling you that there is a convention to be used while naming an object on the type of naming/directory service. According to the DNS naming convention, in xyz.com, xyz is an entry. Which is relative to another entry called com. Here, dot is the separator.

The process of associating a name with an object is called binding. For example, in DNS, machine names are binded with their addresses. In file system, a file named xyz.txt is a binding of the file name and the file.

A group of bindings is called a context. A context helps categorize bindings and makes searching for objects easy. For example, in a Unix file system, assume that there is a directory called Sample Directory under root, with file name file1, file2 and file3. Here, Sample Directory is a context consisting three bindings. So, file1 would be named “/Sample Directory/file1”.

A context can have another within it. The embedded context i called a subcontext. A subcontext is valid only in directory services and not in naming services, as naming services are not arranged in a hierarchical tree-like structure. In terms of file system, a sub-directory is called a subcontext. In the Unix system, if Sample Directory has a sub-directory called Sub Sample Directory, which in turn has a file, example1, the name for example1 relative to the root would be “/Sample Directory/Sub Sample Directory/example1”. In DNS, domains com, edu, etc are contexts created to categorize websites, whereas in “mail.xyz.com”, domain com is a context, domain xyz is a subcontext of com and xyz has a binding where the name mail which is mapped to the IP address of the machine.

A namespace is a set of all the names in naming/directory service.

The name of the object can be a composite name spanning multiple naming/directory services. A composite name can have several components, each of which might belong to a different service.

A compound name is a name that belongs to a naming or directory service. A compound name in a naming service can have only components. For example, a compound name in a DNS service would look like www.ibm.com. However, a compound name in a directory service can have many components because of its hierarchical structure. For example, a compound name in a Unix file system, which is a directory, would look like “/Sample Directory/Sample File”. Compound names are composed according to the naming conversion of that service.

If a component of a name cannot be divided future then it is an atomic name.

Different Naming and Directory Services

Now JBS will tell you some of the popular naming and directory services.

  1. RMI Registry: The RMI registry is a naming service that does not provide directory features and follows a flat system (A flat system is one that does not have a tree like structure). The concept of a sub context is not applicable to the RMI registry. Therefore, a name in a RMI registry can have only one component.
  2. COS Naming: COS Naming is a directory service used by CORBA programs for binding CORBA objects. Here, a compound name can have more than one component.
  3. Network Information Service (NIS): NIS is a directory service from Sun Microsystems, which was earlier called Sub Yellow Pages. It is used to store, information that has to be shared across the network. Information such as user name and password are stored in it to enable clients to login from any machine in the network that has a NIS client running.
  4. IBM WAS: IBM WAS includes a directory service, which offers shared access to Java components. Complex search operations on directory objects are not supported.
  5. Service Using Lightweight Directory Access Protocol (LDAP): LDAP is a protocol that was accepted by some directory services in the market. Services following the LDAP are all accessed in the same way. They use a naming convention where the separator between each component in a name is comma. For example, CN=Printer, OU=Network services, O=headquarters is a name where CN=Pinter is an entry relative to OU=Network service, which in turn is relative to another entry O=Headquarters, In a LDAP name, each component is a name-value pair. Moreover, the name is read from right to left. So, here O=Headquarters is a context having a subcontext OU=Network series. The subcontext OU=Network services has a binding with the name CN=Printer.
Introduction to Java Naming and Directory Interface

JNDI was introduced to provide programmers with a common interface to access different naming and directory services. JNDI is similar to JDBC, which provides a common technique to access different databases. A java programmer making JDBC calls to a database would do so in a standard vendor-independent manner. These calls are same irrespective of the database used. These calls are then passed on to a driver (specific to the database being used), which translates them into calls that can be understood by the database and passes them to the database. The results are sent by the database in a database-specific from to the driver, which are then translated by the driver into a standard form and returned to the Java program.

Applying the same concept to JNDI, a java program can make a JNDI call to access a naming or directory service to retrieve an object registered. The calls are transferred to a service provider that is associated with the naming or directory service being accessed. The service provider knows how to translate the JNDI call to vendor-specific call. The service provider passes the object retrieved from the underlying naming/directory service to the Java program. This object can be written in any language, but when given to the Java program, it will become a Java object.

JNDI Architecture

The JNDI architecture consists of an API that is used by Java applications to access naming and directory services. It also consists of a Service Provider Interface (SPI) implemented by service providers so that they can make available the service they support to the Java application. The following figure illustrates the JNDI architecture.









Going back to JDBC, if a Java program has to access a database using JDBC, then the driver corresponding to the database should be available. Similarly, if a java program has to access a naming/directory service using JNDI, the corresponding service provider for that naming/directory service should be available.

The JNDI is included in java 2 SDK V1.3 and later releases. It also includes service provider for LDAP, COS naming and RMI registry. Thus the corresponding naming/directory service can be accessed through the APIs. To use any other naming/directory service, the corresponding service provider has to be a downloaded from the JNDI website.

Application programs, which need to work with naming and directory services, can use the javax.naming and javax.naming.directory packages. Service providers who wish to provide their implementations so that their corresponding naming/directory service is available to application programs using JNDI can use the javax.naming.spi package.

Environment Properties in details

We know that while trying to create an initialcontext to start working with a naming/directory service, the application using the service has to specify certain properties called environment properties. JNDI defines some standard environment properties, which however, need not apply to all service providers. However, If applicable, the implementation should conform to JNDI specifications. Some service providers have environment properties specific only to their services.

In the above-mentioned examples showing the usage of JNDI to access file system, we have specified two standard environment properties. Let us discuss them now with JBS.

The first standard property is the initial context factory. To create the initialcontext, we have to specify a property called initial context factory. The value for this property would be the fully qualified name of the factory class that will create the initial context. The factory class is a class that knows how to create the initialcontext for the naming/directory service that it is associated with. By creating the initalcontext, we specify which service provider is to be used. For example, for the File system, the fully qualified name of the factory class is com.sun.jndi.fscontext.RefFSContextFactory.

Now there has to be some way of specifying the name of the property. The Context interface defines a constant INITIAL_CONTEXT_FACTORY. This constant has a string value java.naming.factory.initial. This is the name of the property. In a code, both can be used interchangeable, that is, either Context.INITIAL_CONTEXT.FACTORY or the string value java.naming.factory.initial can be used to specify the name of the property. Therefore, in the code showing the usage of JNDI to access file system, the property is Context.INITIAL_CONTEXT_FACTORY (or can also be given as the string java.naming.factory.initial) and the value for the property is com.sun.jndi.fscontext.RefFSContextFactory.

The second standard property in the code defined by the constant PROVIDER_URL specifies the location of the service and the starting point for the initialcontext. The string value associated with this constant is java.naming.provider.url. The value given for this property is in the form of a URL. For example, in the code showing the usage of JNDI to access file system, the protocol specified is file and the starting point is c:\\.

Similarly, for each environment property, the Context interface defines constants that have an associated string value. When specifying the property, either of them can be used as the name for the property. But remember that the properties are always to be specified as a name=value pair, i.e., the name of the property (given either as a constant of as a string corresponding to the constant) and the associated value.

In the above-mentioned examples, the environment properties are specified through a Hashtable. There are also other ways of specifying them.

An application resource file is one that is given the name jndi.properties. This file helps a JNDI program to acquire environment properties that it needs. This file has the environment properties, needed by that JNDI program, specified as key-value pairs. The application resource file should be in the classpath for JNDI program to find it. For example, if the file system is the service being accessed in a JNDI program, then the jndi.properties file has the following content.

java.naming.factory.initial = com.sun.jndi.fscontext.RefFSContextFactory

java.naing.propider.url = file:c:\\

The program accessing the file system would create an initialcontext using the default constructor as follows:

Context contx=new.InitialContext ();

When trying to create the intialcontext, since Hashtable is not passed to the constructor, the JNDI automatically looks for the jndi.properties file and picks up the environment properties. Thus, there is no need for manually coding the environment properties within the program.

DataSource

To write a traditional JDBC client program using the core JDBC API, perform the following steps:

  1. Load the required driver class.
  2. Get a connection to the database.
  3. Execute SQL queries.
  4. Close the connection.

The above architecture has the following drawbacks:

  1. The driver name, URL, etc. are embedded in the program. Thus, changing the database is very inconvenient.
  2. The client program is responsible for loading the driver and thus, the client program should be aware of the driver name.
  3. When a connection is established, a lot of resources are used.
  4. The connection, which is created with so much effort, can be used only once and cannot be reuse.

JDBC 2.0 Optional Package API overcomes the above-mentioned problems by supporting a concept called DataSource. DataSources offer the following advantages while writing a program to access databases:

  1. The client program need not remember the driver name, URL, etc.
  2. Connection can be reused through connection pooling
  3. Code portability and maintenance is improved.

First, a DataSource object is created with the details required to establish a connection to a database. This object is then bound to a textual name in a JNDI service. Using JNDI API, the client program can contact the JNDI service and lookup the DataSource object by specifying its name. From this DataSource object, a connection to the database is acquired. Thereafter, the operations are the same as any traditional JDBC program. The advantage here is all the details required to create a connection is supplied at the time of creation of a DataSource. The client program need not be aware of any of the connection details. The DataSource object can be obtained from the JNDI service and then a connection can be acquired from it. The only detail the client program needs to know is the JNDI name of the DataSource.

Creating and Using DataSources with JBS

Application server such as WAS and WebLogic support DataSources. These application servers provide a GUI through which information required to create a DataSource object (such as driver-name, URL for the database, database, database) can be supplied to the application server by the programmer, before writing the code to access the DataSource object. The name to be mapped with the DataSoruce object when it is bound in the JNDI service is also given by the programmer along with the above mentioned details. The application server itself usually has a JNDI service.

The JDBC 2.0 Optional Package API provides the javax.sqp.DataSource interface. When the server is started, it creates an object of a class that implements javax.sql.DataSource with the information entered earlier by the programmer through the GUI. The sever then binds the DataSource object with its JNDI service. To establish a connection with the databse, which the DataSource refers to, client programs use JNDI API to first lookup the DataSource object by its name. Once this object is found, a connection to the database is established.

The steps to create a DataSource object and access a database are as follows

  1. Create an initialcontext for the JNDI service. The environment properties supplied would be relevant to the application server whose JNDI service is being used.
  2. Perform a lookup through the lookup() method using the initialcontext. The JNDI name of the DataSource object is supplied to the lookup() method.
  3. The lookup() method returns an object of type java.lang.Object. Typecast the object to javax.sql.DataSource.
  4. The getConnection() method of java.sql.DataSource interface is invoked on the DataSource object, which returns a java.sql.Connection object. However, the internal behavior of this object is different from the java.sql.Connection object that is normally used in JDBC program.

Once the Connection object is acquired, the code for the rest of the program is similar to that of any other JDBC program. One main difference is that while java.sql.DriverManager class is used in the code using normal JDBC to acquire a connection, in the code using DataSource, javax.sql.DataSource is used. Moreover, now here does the client program specify the driver name or load the driver. Assume there is a dataSource registered in the name jdbc/sampledatasource in a JNDI service.

Connection Pooling

One main advantage of using DataSource is that they support connection pooling. A connection pool is a cache of persistent database connections maintained in the memory. Connection pooling allows connections to be reused. If an application needs a database connection, it would contact the pool and acquire a connection. Immediately after completing the SQL operations, it returns the connection to the pool. Later another application, or the same application, can contact the pool for a connection object. The recently used connection object or another from the pool will be given to fulfill the demand.

Thus, the connections are not broken after an application stops using them. Instead they are reused by the same application or by other applications, which improves performance. The pool actually maintains java.sql.Connection objects. However, the implementation of these Connection objects is different from that of the Connection objects used in a normal JDBC program.

The questions arises as to why should there be a pool of connections? Should not just one sufficient? There could be several applications simultaneously in need of a database connection to the same database. So, when one application is using a connection from the pool, another application requiring a connection object should not be made to wait. A free connection from the pool can be given to it.

A connection pool usually has a maximum limit and holds a certain number of connections only. If the demand crosses this limit, then the applications are made to wait until one of the connections is released back to the pool. The application server in which the DataSource is created and bound is responsible for the creation and maintenance of the pool.

Not all servers support connection pooling. Under such circumstance, when an application acquires a DataSource object and requests a Connection object, the DataSource creates a Connection object and gives it to the application. However, this connection cannot be reused, as the server does not support connection pool.

An application does a lookup and acquires a DataSource object. When the getConnection() method is invoked on the DataSource object. The connection pool is contacted; a connection object from this pool is picked and returned to the application. The application can use this connection object and when it does not require it any more it invokes the close() method of the Connection object.

Thus the steps it use DataSoruce would be:

  1. Enter information such as driver name, URL for the database, database name, and minimum and maximum limit for connection pool through a GUI provided by the application, to create a DataSoruce.
  2. Start the server.
  3. DataSource gets bound to the JNDI service of the application server.
  4. If the server supports connection pooling, then it creates a connection pool with connection object, each of which maintain a connection to the database.
  5. An application that needs a connection to the database, requests for a DataSource object associated with that database.
  6. The getConnection() method is invoked on the DataSource.
  7. If the server supports a connection pool, the DataSource contacts the pool manager and the pool manager gives a Connection object. Otherwise the DataSource Object itself creates a Connection object and returns it.
  8. Application calls close() method on the Connection object.
  9. The Connection object is returned to the connection pool, if there is one. Otherwise the connection is closed.

When creating a DataSource through the GUI, supported by an application server, the minimum and maximum number of live connections to be maintained in a connection pool must also be specified. When be server is started and the DataSource object is bound to its JNDI service, a connection pool is established corresponding to the DataSource. There is usually a pool manager object responsible for maintaining the pool. The pool will have at least the minimum number of Connection objects maintain a persistent connection with the database to which the DataSource is associated.

1 comment: