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.
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"); } }
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.
import java.util.Vector; import javax.naming.Context; public interface Command { public void execute(Context c, Vector v) throws CommandException; public void help(); }
public class CommandException extends Exception { Exception e; // root exception CommandException(Exception e, String message) { super(message); this.e = e; } public Exception getRootException() { return e; } }
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.
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.
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
Copyright © 2001 O'Reilly & Associates. All rights reserved.