The NamingShell Application (Java Enterprise in a Nutshell) Book Home Java Enterprise in a Nutshell Search this book

6.5. The NamingShell Application

Earlier, we discussed how we might modify the Lookup example to make it more general, allowing us to look up Enterprise JavaBeans and remote objects. The rest of the examples in this chapter are going to be based on the NamingShell code shown in Example 6-2. NamingShell is an extensible JNDI shell that enables us to perform naming operations in any JNDI-accessible naming system. The shell provides methods for getting and setting the current object and other shell-related details, and it also keeps track of the name of the current object, something a Context cannot do for itself.

Once you have loaded NamingShell, you can use the shell to execute JNDI-related commands, just as you would use a regular shell to execute operating-system commands. I encourage you to download the code for NamingShell right now, so that you can experiment with it as we proceed through the rest of the chapter. NamingShell uses the name you type to locate a command dynamically from the filesystem. The shell has no interpreter; however, NamingShell expects a command to implement the Command interface and its execute() method. This means a command really interprets itself. A command throws a CommandException when execution fails.

As you can see, NamingShell itself contains very little real JNDI code. All the JNDI functionality is implemented in the various Command classes we create to handle particular JNDI operations. The shell simply supports the loading of commands and keeps track of various shell-related details.

Example 6-2. The NamingShell Class

import java.io.*;
import java.util.*;
import javax.naming.*;
 
class NamingShell {
    
  // Private variables
  private static Hashtable COMMAND_TABLE = new Hashtable();
  private static String  JNDIPROPS_FILENAME  = ".jndienv";
  private static String  PROMPT = "[no initial context]";
  private static String  VERSION = "1.0";
  private static Context CURRENT_CONTEXT, INITIAL_CONTEXT;
  private static String  CURRENT_NAME, INITIAL_NAME;
  private static boolean RUNNING = true;

  // Shell operations
  private static void exit(int status) { System.exit(status); }
    
  // Accessor methods
  public static Hashtable getCommands() { return COMMAND_TABLE; }
  public static Context getCurrentContext() { return CURRENT_CONTEXT; }
  public static String getCurrentName() { return CURRENT_NAME; }
  public static String getDefaultPropsFilename() { return JNDIPROPS_FILENAME; }
  public static Context getInitialContext() { return INITIAL_CONTEXT; }
  public static String getInitialName() { return INITIAL_NAME; }
  public static String getPrompt() { return PROMPT; }
  public static void setCurrentContext(Context ctx) { CURRENT_CONTEXT = ctx; }
  public static void setInitialContext(Context ctx) { INITIAL_CONTEXT = ctx; }
  public static void setInitialName(String name) { INITIAL_NAME = name; }    
  public static void setPrompt(String prompt) { PROMPT = prompt; }
  public static void setCurrentName(String name) {
    CURRENT_NAME = name;
    setPrompt(name);
  }

  // Executes a preinstantiated command we are sure is already
  // present in the table
  private static void execute(Command c, Vector v) {
    if (c == null) {
      System.out.println("No command was loaded; cannot execute the command.");
      return;
    }
    try {
      c.execute(CURRENT_CONTEXT, v);
    }
    catch (CommandException ce) {
      System.out.println(ce.getMessage());
    }
  }
    
  // Another private method that enables us to specify a command
  // by its string name and that loads the command first
  private static void execute(String s, Vector v) {
    execute(loadCommand(s), v);
  }
            
  // Loads the command specified in commandName; the help command
  // relies on this method
  public static Command loadCommand(String commandName) {
    // The method returns a null command unless some of its 
    // internal logic assigns a new reference to it
    Command theCommand = null;
        
    // First see if the command is already present in the hashtable
    if (COMMAND_TABLE.containsKey(commandName)) {
      theCommand = (Command)COMMAND_TABLE.get(commandName);
      return theCommand;
    }

    try {
      // Here we use a little introspection to see if a class
      // implements Command before we instantiate it
      Class commandInterface = Class.forName("Command");
      Class commandClass = Class.forName(commandName);
            
      // Check to see if the class is assignable from Command 
      // and if so, put the instance in the command table
      if (!(commandInterface.isAssignableFrom(commandClass))) 
        System.out.println("[" + commandName + "]: Not a command");
      else {
        theCommand = (Command)commandClass.newInstance();
        COMMAND_TABLE.put(commandName, theCommand);
	return theCommand;
      }
    }
    catch (ClassNotFoundException cnfe) {
      System.out.println("[" + commandName + "]: command not found");
    }
    catch (IllegalAccessException iae) {
      System.out.println("[" + commandName + "]: illegal acces");
    }
    catch (InstantiationException ie) {
      System.out.println("["+commandName+"]: command couldn't be instantiated");
    }
    finally {
      return theCommand;          // theCommand is null if we get here
    }
  }
    
  // This method reads a line of input, gets the command and arguments
  // within the line of input, and then dynamically loads the command
  // from the current directory of the running shell
  private static void readInput() {
    // Get the input from System.in
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));  
        
    // Begin reading input
    try {
      while (RUNNING) {
        System.out.print(PROMPT + "% ");
                
        // Tokenize the line, read each token, and pass the token
        // into a convenient remaining arguments Vector that we
        // pass into the Command
        StringTokenizer tokenizer = new StringTokenizer(br.readLine());
        Vector remainingArgs = new Vector();
        String commandToken = "";
        if (tokenizer.hasMoreTokens()) {
          commandToken = tokenizer.nextToken();
          while (tokenizer.hasMoreTokens())
            remainingArgs.addElement(tokenizer.nextToken());
        }
                
        // Dynamically load the class for the appropriate command
        // based upon the case-sensitive name of the first token,
        // which is the command token
        if (!(commandToken.equals(""))) 
          execute(commandToken, remainingArgs);
      }
    }
    catch (java.io.IOException ioe) {
      System.out.println("Caught an IO exception reading a line of input");
    }
  }
  // Constructor
  NamingShell(String[] args) {
  }
    
  // Main method that reads input until the user exits
  public static void main(String[] args) {
    System.out.println("NamingShell " + VERSION);
    System.out.println("Type help for more information or exit to quit");
    shell.readInput();
    System.out.println("Exiting");
  }
}

6.5.1. The Command Interface

The Command interface (shown in Example 6-3) describes a standard interface for a shell command. It has an execute() method that contains the command logic and a help() method for displaying online help for the command. If execute() encounters a naming exception (or some other exception), it throws a CommandException (shown in Example 6-4), which stores the first exception as an instance variable so that the shell can display the exception appropriately.

Example 6-3. The Command Interface

import java.util.Vector;
import javax.naming.Context;

public interface Command {
  public void execute(Context c, Vector v)
    throws CommandException;
  public void help();    
}

Example 6-4. The CommandException Class

public class CommandException extends Exception {
  Exception e; // root exception
  CommandException(Exception e, String message) {
    super(message);
    this.e = e;
  }
  public Exception getRootException() {
    return e;
  }
}

6.5.2. Loading an Initial Context

As I said earlier, to use JNDI to look up an object in a naming system (or, in fact, to do anything with the naming system), you first have to create an InitialContext for that naming system. So, the first command we need to implement is initctx, for loading an initial context into NamingShell. Example 6-5 shows an implementation of this command.

Example 6-5. The initctx Command

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

public class initctx implements Command {
    
  public void execute(Context c, Vector v) {
    String jndiPropsFilename;
    // If no properties file is specified, use the default file;
    // otherwise use the specified file
    if (v.isEmpty())
      jndiPropsFilename = NamingShell.getDefaultPropsFilename();
    else
      jndiPropsFilename = (String)v.firstElement();
            
    try {
      Properties props = new Properties();
      File jndiProps = new File(jndiPropsFilename);
      props.load(new FileInputStream(jndiProps));
           
      NamingShell.setInitialContext(new InitialContext(props));
      NamingShell.setInitialName("/");
      NamingShell.setCurrentContext(NamingShell.getInitialContext());
      NamingShell.setCurrentName(NamingShell.getInitialName());
      System.out.print("Created initial context using ");
      System.out.println(jndiProps.getAbsolutePath());
    }
    catch (NamingException ne) {
      System.out.println("Couldn't create the initial context");
    }
    catch (FileNotFoundException fnfe) {
      System.out.print("Couldn't find properties file: ");
      System.out.println(jndiPropsFilename);
    }
    catch (IOException ioe) {
      System.out.print("Problem loading the properties file: ");
      System.out.println(jndiPropsFilename);
    }
    catch (Exception e) {
      System.out.println("There was a problem starting the shell");
    }
  }
    
  public void help() { System.out.println("Usage: initctx [filename]"); }
}

The initctx command accepts an argument that specifies the name of a properties file to use in creating the Properties object that is passed to the InitialContext constructor. If no filename is specified, initctx looks for the default properties file specified by NamingShell. So, with NamingShell, all you have to do to use a particular naming service is create an appropriate properties file for that service.

6.5.3. Running the Shell

With NamingShell and initctx, we have enough functionality to actually run the shell. Before you try running the shell, make sure that the JNDI libraries (in jndi.jar) and any other specialized providers are specified in your classpath. Here's how we might start NamingShell and establish an initial context, once the classpath is set appropriately:

% java NamingShell
NamingShell 1.0
Type help for more information or exit to quit
[no initial context]% initctx
Created initial context using C:\temp\samples\book\.jndienv
/%

In this case, since we didn't specify a properties file, NamingShell looks for the .jndienv file in the current directory. For the purpose of our next few examples, let's assume that this file contains property settings that allow us to use the filesystem provider from Sun. You can change initial contexts at any time during the shell session by running initctx with a new filename. After you have created an initial context, you can begin performing naming operations by typing in commands. To exit the shell, simply use the exit command.[2] If you are not sure how a command works, you can get help for that command by typing:

[2]The help and exit commands are implemented as separate classes, just like the JNDI-related commands. We've not going to examine the code for these commands, as they don't use JNDI. However, the code for these commands is provided in the example code that is available online (at http://www.oreilly.com/catalog/jentnut/ ).

/% help command


Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.