Java Enterprise in a Nutshell (In a Nutshell (OReilly))
9.5. The NamingShell Application
The rest of the examples in this chapter are going to be based on the NamingShell code shown in Example 9-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 can't do for itself. Example 9-2. The NamingShell class
import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.Hashtable; import java.util.StringTokenizer; import java.util.Vector; import javax.naming.Context; class NamingShell { // Private variables private static Hashtable COMMAND_TABLE = new Hashtable(); 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; private static String CMD_PKG = "com.oreilly.jent.jndi"; // 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 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(CMD_PKG + ".Command"); Class commandClass = Class.forName(CMD_PKG + "." + 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); } } catch (ClassNotFoundException cnfe) { System.out.println("[" + commandName + "]: command not found"); } catch (IllegalAccessException iae) { System.out.println("[" + commandName + "]: illegal access"); } catch (InstantiationException ie) { System.out.println("["+commandName+"]: command couldn't be instantiated"); } return theCommand; } // 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) { NamingShell shell = new NamingShell(args); NamingShell.readInput(); System.out.println("Exiting"); } } Once you've 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. We 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 classpath. 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. 9.5.1. The Command Interface
The Command interface (shown in Example 9-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 9-4), which stores the first exception as an instance variable so that the shell can display the exception appropriately. Example 9-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 9-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; } }
9.5.2. A Command for Loading an Initial Context
As we 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 9-5 shows an implementation of this command. Example 9-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 = null; // Check for a properties filename if (!v.isEmpty( )) jndiPropsFilename = (String)v.firstElement( ); System.out.println("file = " + jndiPropsFilename); try { // If no properties file is specified, let JNDI get its properties from // the default jndi.properties file on the CLASSPATH. Otherwise, use // the specified properties file. if (jndiPropsFilename != null) { Properties props = new Properties( ); File jndiProps = new File(jndiPropsFilename); props.load(new FileInputStream(jndiProps)); NamingShell.setInitialContext(new InitialContext(props)); } else { NamingShell.setInitialContext(new InitialContext( )); } NamingShell.setInitialName("/"); NamingShell.setCurrentContext(NamingShell.getInitialContext( )); NamingShell.setCurrentName(NamingShell.getInitialName( )); System.out.print("Created initial context using "); if (jndiPropsFilename != null) { System.out.println(jndiPropsFilename); } else { System.out.println("jndi.properties."); } } 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 optional 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 creates the InitialContext using the no-argument constructor, which in turn uses the default jndi.properties file in the CLASSPATH. So, with NamingShell, all you have to do to use a particular naming service is create an appropriate properties file for that service and either specify it explicitly when you invoke the initctx command or put it in your CLASSPATH as jndi.properties. 9.5.3. Running the Shell
With NamingShell and initctx, we have enough functionality to actually run the shell. Before you try this, make sure that any 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 [no initial context]% initctx Created initial context using jndi.properties /%
In this case, since we didn't specify a properties file on the command line, the no-argument InitialContext constructor is called, and it looks for a jndi.properties file in the CLASSPATH. 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 create an initial context, you can begin performing naming operations by typing in commands. To exit the shell, simply use the exit command.[*] If you aren't sure how a command works, you can get help for that command by typing: [*] The help and exit commands are implemented as separate classes, just like the JNDI-related commands. We don't examine the code for these commands, because they don't use JNDI. However, the code is provided in the examples available online at http://www.oreilly.com/catalog/javaentnut3. /% help command
|