Context Operations

After a client connects to the JNDI service and obtains a JNDI context using InitialContext(), it can apply any of the interface methods. The main operations of the javax.naming.Context interface are the use of bind() to add an entry, rebind() to replace an entry name, lookup() to find or locate an object, and unbind() to delete an entry. Figure 4.3 illustrates these operations. Logically, the server is the application that performs the binding, unbinding, and rebinding operations. Clients perform the lookup of objects by providing the name.

Figure 4.3. JNDI context operations.


The next sections give more details about the Context operations.

Add an Entry—bind()

Servers add an object or a component into the JNDI tree by binding its name to its location or reference. Here is a scenario of binding a new object to its name. First, a Context object must be obtained by using

Context ctx = new InitialContext();

When InitialContext() is used with no parameters, the application is connected to the default JNDI service provider. This can be set in the configuration files as explained later today in the section “Selecting a JNDI Provider.” Using no parameters also indicates the use of the default user or anonymous user. For a secure system, only certain components or resources must be authenticated first. The section “Using JNDI in User Authentication” later today gives an example of how to connect with a specific user.

Now, you can add a new name by using an example of binding a new object as implemented as follows:

String name = "mary";
String email = "[email protected]"
ctx.bind(name, email);

If the name "mary" already exists, the exception NameAlreadyBoundException will be thrown.

Applications and services can use JNDI service in different ways to store and look up objects. For example, an application might store a copy of the object itself, a reference to an object, or the attributes of the object. Objects that must be remotely accessed (through the use of Remote Method Invocation or RMI) must be in a serialized form (that is, implement the java.io.Serializable interface) that contains the object's serializable objects that can be marshalled and unmarshalled between remote servers.

Note

RMI is a protocol that enables an object on one JVM (Java Virtual Machine) to invoke methods on another object in different JVM. Any object whose methods can be invoked in this way must implement the java.rmi.Remote interface.

When such an object is invoked, its arguments are marshalled (converted to a bit stream) and sent from the local JVM to the remote one, where the arguments are unmarshalled and used. When the method terminates, the results are marshalled from the remote machine and sent to the caller's virtual machine.


Delete an Entry—unbind()

Applications can also delete entries by using unbind() of an object from the JNDI service, provided that a context is already obtained. For example, the line

ctx.unbind("mary");

will remove the binding established in the previous section.

Find an Entry—lookup()

One of the most common operations of JNDI is the lookup() method, which is used to locate or find an object. Provided that a context is already obtained, here is an example of looking up an object:

String str = (String) ctx.lookup("mary");

The lookup() operation returns an java.lang.Object, which must be cast to the required object's class. In the previous example, lookup is cast to String, and the value of str will be “[email protected]”. If the name is not found, the exception javax.naming.NamingException will be thrown.

To look up an object in the JNDI service, the name must be provided as a parameter to the lookup() operation. The returned object is cast to the known object class. Now any operation can be performed on that object. The following example illustrates the power of the naming services as a method of dynamically binding a name to the real object:

try {
    // Connect to JNDI and create the initial context
    Context ctx = new InitialContext();
    // Perform lookup and cast to target class
    File f = (File) ctx.lookup("myfile.txt");
    f.open();
    // ...do something with the file...
    f.close();
    // Close the context when we're done
    ctx.close();
} catch (NamingException e) {
    System.out.println("Lookup failed: " + e);
}

Table 4.1 summarizes the JNDI operations that can be applied to the context. These operations throw the javax.naming.NamingException that must be captured in a catch clause. A class hierarchy for exceptions is defined as part of the JNDI API. These exceptions can be thrown in the course of performing naming operations. The root of this class hierarchy is NamingException. Programs can catch generally the NamingException or, specifically, any other exception in the class hierarchy.

Each of these methods accepts a name of type java.lang.String as a parameter, and has an overloaded method with a name of type javax.naming.Name. For example, the lookup() method has the following signatures:

lookup(java.lang.String name)

lookup(javax.naming.Name name)

The javax.naming.Name interface represents a generic name as an ordered sequence of name components. It can be a composite name (one that spans multiple namespaces) or a compound name (one that is used within a namespace).

Table 4.1. Summary of Context Operations
MethodPurpose
Context InitialContext()Connects to the default JNDI service and establishes a new context
Context InitialContext(Properties p)Connects to the a specific JNDI service and establishes a new context
void bind (Name name, Object obj)Binds or adds a new name/object association
void bind (String name, Object obj)Binds or adds a new string/object association
void rebind (String name, Object obj)Rebinds or replaces an existing string/object association
void unbind (Name name)Unbinds or removes the binding of an object to an associated name
Object lookup(Name name)Looks up an object in the naming service using a name
void rename (String oldName, String newName) Changes the name to which an object is bound
NamingEnumeration listBindings (Name contextName)Enumerates all the names bound in the context name, along with the objects bound to them.
NamingEnumeration listBindings (String contextName)Enumerates all the names bound in the context name, along with the objects bound to them.
void close()Disconnects from the JNDI service, and is used to free resources used by a context

Example of Using JNDI Context Operations

The following program demonstrates some of the operations listed in Table 4.1. Listing 4.1 creates an initial context from the default JNDI provider, lists the environment, adds a new entry, and then finds it by looking up the entry. To query the existing environment, you can use the getEnvironment() method of the Context interface. The CommunicationException is thrown if the operation fails to connect to the JNDI service.

Listing 4.1. The Full Text of day04/Client.java
package day04;

import java.util.*;
import java.rmi.*;
import java.io.*;
import javax.naming.*;
import javax.ejb.*;

// This client demonstrates a sample usage of the JNDI tree

public class Client{
    public static InitialContext ctx;
 public static void main(String[] argv)  {
    print("Demonstration of the usage of JNDI...");
    if(argv.length < 1){
        print("Usage : Client <JNDI root name>
");
        return;
    }
    try    {
      print("Connecting to a JNDI service...");
      ctx = new InitialContext();
      print("  Connected successfully. Initial context created.
");
      print("Getting Environment Properties...");
      print("  Properties: " + ctx.getEnvironment().toString() + "
");
      // Adding a binding
      String name = "mary";
      String email = "[email protected]";
      print("Binding a new name: " + name + " to an object: "+email+"...");
      ctx.bind(name, email);
      print("  Object: "+ email+ " is bound to name: " + name + "
");
      // Lookup a binding
      print("Looking up the name...");
      String s = (String) ctx.lookup("mary");
      print("  Found Name= mary, with email= " + s + "
");
      // Delete a binding
      print("Unbinding the name...");
      ctx.unbind("mary");
      print("  Name is unbound successfully!
");
      print("Spanning JNDI context bindings...");
      spanJNDI(argv[0]);
      print("
");
      // Lookup a "deleted" binding
      print("Lookup for the unbound name...error expected");
      s = (String) ctx.lookup("mary");
      print("  Found Name= mary, with email= " + s);

    }
     catch (CommunicationException e) {
        print("**ERROR: Failed to connect with the JNDI server." +
          "Startup the App Server, and run again.."+e);
      }
      catch (Exception e) {
        print("**ERROR: An unexpected exception occurred..."+e);
      }
      finally {
        if (ctx != null) {
          try {
            print("Unbinding the name...");
             ctx.unbind("mary");
            ctx.close();
            print("Connection to JNDI is closed successfully.");
          }
          catch (NamingException e) {
            print("**ERROR: Failed to close context due to: " + e);
          }
        }
      }
  }

  static void spanJNDI(String name){
    try{
      ctx = new InitialContext();
      NamingEnumeration bindList = ctx.listBindings(name);
      // Go through each item in list
      while (bindList !=null && bindList.hasMore()) {
          Binding bd = (Binding)bindList.next();
        print("  " + bd.getName() + ": " + bd.getClassName() + ": " + bd.getObject());
        spanJNDI(bd.getName());
      }
     }catch (NamingException e) {

     }
  }
  static void print(String s)  {
     System.out.println(s);
  }
 }

This example is made available to run on both the WebLogic and JBoss servers. To run this example on either of the servers, you must have the server installed and set up your environment to run the server and the example. The accompanying Readme.txt file will help you perform these steps.

When you run this example on the WebLogic server, the output should look like the following:

  > {java.naming.provider.url=t3://localhost:7001, java.naming.factory.initial=weblogic
.jndi.WLInitialContextFactory}
  > javax: weblogic.jndi.internal.WLContextImpl: WLContext (javax)
  > weblogic: weblogic.jndi.internal.WLContextImpl: WLContext (weblogic)
  > java:comp: weblogic.jndi.internal.WLContextImpl: WLContext (java:comp)
Binding name:maryto an object: [email protected]...
  > Object: [email protected] is bound to name: mary
  > Found Name= mary, with email= [email protected]
  > Name is unbound sucessfully!
*ERROR: An unexpected exception occurred...
   javax.naming.NameNotFoundException: Unable to resolve mary.
   Resolved: '' Unresolved:'mary' ; remaining name ''
*ERROR: Connection to JNDI is close successfully.

The output lists the environment of the JNDI service (in this case, WebLogic) and all the existing bindings. The error was produced when an exception was raised because we tried to look up an entry that was not found. The error was captured and an error message was displayed. For your convenience, the WebLogic Server environment provides a JNDI browser, which you can access using the WebLogic Console. This is accomplished by pointing with your Web browser to the URL http://localhost:7001/console, (provided that WebLogic Server is running). From the left pane, click on myserver, and then on the right pane click on View JNDI Tree. A new window will pop up displaying a list similar to the listing of the example output.

Running the same example on JBoss should produce a similar output, except that the environment and the bindings will be specific to JBoss environment. You can also access the JBoss JNDI tree by pointing to the URL http://localhost:8082 (if you are using JBoss 3.0.0), or http://localhost:8080/jmx-console (if you are using JBoss 3.0.1 or later). Click on the link service=JNDIView, and then invoke the list() operation.

Specifying a JNDI Provider

For clients to access a JNDI service, programs must specify the provider name and the location of the JNDI service on the network. Programs can specify this environment setting in either a programmatic or a declarative method.

Programmatic Method

Setting Context.INITIAL_CONTEXT_FACTORY specifies the JNDI provider, and setting Context.PROVIDER_URL specifies the URL location. The following sample code illustrates the access to a JNDI service from inside your code:

// Set JNDI environment in the properties
Properties prop = new Properties();
// Set the JNDI provider as WebLogic
prop.put(Context.INITIAL_CONTEXT_FACTORY,
           "weblogic.jndi.WLInitialContextFactory");
// set the JNDI URL (host name and port) to access the service
prop.put(Context.PROVIDER_URL, "t3://localhost:7001");
// Connect to the JNDI service as specified above
Context ctx = new InitialContext(prop);

Caution

The last call establishes a connection to the default JNDI service and creates an Enterprise Naming Context (ENC). Naming services usually reside on a remote server on the network. Clients remotely access these services using RMI protocol, which is more expensive than to access these services locally.


The Properties object specifies both the JNDI provider (in this case, WebLogic Server) and the location of the naming services on the network.

Declarative Method

The programmatic method is not a good practice in developing large projects. The declarative method is convenient for setting these environment parameters, instead of hard-coding them in the client code itself. Using the declarative approach avoids recompilation, which results from changing the existing code, should you decide to switch from one JNDI provider to another.

The environment parameters can be specified in an application resource file, or can be passed as the -D command-line option to the Java interpreter. In the first case, the JNDI provider parameters can be set in a special application resource file jndi.properties. It contains a list of key/value pairs presented in the properties file format (which is typically mapped to java.util.Properties used inside the program). A sample of the content of jndi.properties file for the WebLogic server:

java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
java.naming.provider.url=t3://localhost:7001

In JBoss, environment properties are set in the following sample of jndi.properties:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.provider.url=jnp://localhost:1099
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces

This simplifies the task of setting up the environment required by a JNDI application; you may distribute application resource files along with application components and service providers. The key is the name of the property (for example, java.naming.factory.object) and the value is a string in the format defined for that property.

The other option in setting these parameters is to pass them as system properties. These properties can be made available to a Java program via the -D command-line option to the Java interpreter. Here's an example of the WebLogic server command line:

java -Djava.naming.factory.initial=weblogic.jndi.WLInitialFactory 
     -Djava.naming.provider.url=t3://localhost:7001

When using JBoss, the command line looks like the following:

java -Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory 
     -Djava.naming.provider.url=localhost:1099 
     -Djava.naming.factory.url.pkgs=org.jboss.naming

If you use any of the declarative methods mentioned, the client application will use the default InitialContext constructor to create a context (without specifying any parameters). This makes your application more portable to any other application server.

Context ctx = new InitialContext();

Here, the JNDI automatically reads the application resource files from all defines in the applications' class paths and JAVA_HOME/lib/jndi.properties, where JAVA_HOME is the file directory that contains your JRE (Java Runtime Environment). The JNDI then makes the properties from these files available to the JNDI providers and other components that need to use them. Therefore, these files should be considered world-readable and should not contain sensitive information, such as clear text passwords.

Using JNDI in User Authentication

In addition to using JNDI as a naming service, it is common to use it to provide a security mechanism for authenticating users to access system resources. User information and credentials (passwords) can be stored as entries in the directory service. It worth pointing out the fact that JNDI does not define a security model, but uses the security model that is defined in the underlying naming/directory service.

As you learned earlier today, the default InitialContext (with no parameters specified), creates a new Context used by an anonymous client.

// Connect to the default naming server as a default user
Context ctx = new InitialContext();

When authenticating a user, both user ID and password must be combined and passed as a parameter to the InitialContext. If the password is not matched, the exception javax.naming.NoPermission will be thrown to the client. Here is an example of authenticating a user with the WebLogic default JNDI server:

// Set up the environment properties for the user
Properties prop = new Properties();
// Login use both user id, and password passed as Strings
prop.put(Context. SECURITY_PRINCIPAL, "Laura");
prop.put(Context. SECURITY_CREDENTIALS, "whiskers");
Context ctx = new InitialContext(prop);

System resources and components can be protected by Access Control Lists (ACLs), which in turn can be accessed by the authorized user's Context. By creating a context for a particular user, the application server is responsible to verify the appropriate usage of these resources by the correct user.

In the preceding code sample, Context.SECURITY_PRINCIPAL is a constant that specifies the name of the user or program that is doing the authentication. Context.SECURITY_CREDENTIALS is a constant that specifies the credentials of the user or program doing the authentication.

A similar code can be established to authenticate users using the JBoss server. The only changes will be in setting both Context.INITIAL_CONTEXT_FACTORY and Context.PROVIDER_URL to the right values.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.29.119