Extending the Shell


However useful the JXTA Shell is, you'll often find it lacking certain necessary capabilities. For instance, we've already encountered the shell's lack of document-creation capability: We had to export and then import an advertisement to convert it to a document suitable for adding to a message. The JXTA Shell addresses these limitations with an extensibility framework, enabling any user to add custom shell commands. In this section, we will create a new shell command that computes prime numbers using a set of remote prime-searching peers. Thus, this shell command performs the equivalent of the JXTA client we constructed in Chapter 16, except that it serves up that functionality as a JXTA command.

The new command's name will be primefind, and it will take as arguments the integers delimiting the list of prime numbers to produce. After the command is invoked, it will discover peers offering the prime-finding service, distribute the list of primes between them, and wait to receive the results. After the results arrive, the command produces an output consisting of the list of all primes between the argument integers. An sample invocation is as follows:.

 JXTA>primefind 10 100  11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97 

A JXTA shell extension is a Java program with a few special requirements. The program must occupy a subpackage in the net.jxta.impl.shell.bin hierarchy, and that subpackage's name must correspond to the command's name. In addition, the Java class implementing the command must also assume a name identical to the command's name. Thus, the fully qualified class name for the primefind command will be net.jxta. impl.shell.bin.primefind.primefind. The JXTA Shell uses Java's reflection capability to invoke the command based on a user's command-line input.

In addition, the program must be an extension of net.jxta.impl.shell.ShellApp. ShellApp in turn implements the net.jxta.platform.Application interface, which is a type of net.jxta.platform.Module. Module defines three methods corresponding to the life cycle of a JXTA shell extension.

A shell extension is loaded at the time a user invokes its corresponding command. Two methods in Module address a module's and a ShellApp's initialization, and one method enables a module's execution to be halted. The following method initializes the module as it is loaded:

 init(PeerGroup group, ID assignedID, Advertisement implAdv)  

Following that, the int startApp(java.lang.String[] args) method is called. The arguments array for startApp() corresponds to the command-line arguments specified by the user. For the primefind command, there must be two elements: the low and high limits of the primes list. StartApp() returns a status indicator integer; if all goes well, that integer is the constant ShellApp.appNoError. Finally, the module can be stopped with a call to stopApp(). Our implementation of the primefind command will override only the startApp() method.

Another requirement for a shell command is that it must implement ShellApp's getDescription() and help() methods. The former should return a line of text, offering a brief description of the command. When a user types the man command on the JXTA Shell's command line, that line of text will display next to the command's name. If a user wants to obtain a more detailed command description and types man primefind, the text displayed as the man page is obtained from the command's help() method.

Finally, a shell command must be able to access the shell's standard output and input streams. That last requirement is attained via a number of instance variables a shell extension inherits from ShellApp.

Writing a New Shell Command

With these requirements in mind, we are ready to start coding the new shell command. We will reuse most of the code developed in Chapter 16, except for the graphical user interface elements. We start out by implementing the two information-providing methods (see Listing 17.3).

Listing 17.3 The Information-Providing Methods help() and getDescription()
 package net.jxta.impl.shell.bin.primefind; .... public class primefind extends ShellApp {     public void help() {         println("NAME");         println("     primefind   find prime numbers between two integers");         println(" ");         println("SYNOPSYS");         println(" ");         println("     primefind X Y");         println(" ");         println("DESCRIPTION");         println("'primefind' utilizes peers offering a prime number finding ");         println("service to search prime numbers in a distributed manner.");         println("When invoked, 'primefind' discovers peers offering that ");         println("service, distributes the list of numbers between the two");         println("parameters among those peers, and waits for the peers to");         println("respond with their results. Once all results are obtained,");         println("'primefind' produces a comma-separated list of prime numbers");         println("on its standard output. Note that this command relies on ");         println("other peers to search prime numbers; If no peer offering");         println("that service is available, 'primefind' will block until ");           println("at least one such peer is discovered.");         println(" ");         println("EXAMPLE");         println(" ");         println("    JXTA>primefind 10 100");         println(" ");         println("This command produces a list of all prime numbers between");         println("10 and 100 on its standard ouput.");         println(" ");     }     public String getDescription() {         return "Search for prime numbers";     }     public int startApp(String[] argv) {           ....           return ShellApp.appNoError;     } } 

The import statements are available from the full source code, which you can download from this book's Web site. In this code, we simply print the needed information to the shell's standard output via the println method, inherited from ShellApp. After you compile the code, you must generate a JAR file for the binaries; that file serves as the classpath the shell attempts to load the extension's classes from. Thus, you need to ensure that that JAR file contains all the classes used by the shell extension.

With the primefind command's JAR file ready, we can install the new extension to the JXTA Shell. The Shell's instjar command facilitates the loading of new extensions:

 JXTA>instjar /export/home/myfiles/primefind.jar  

Having installed primefind, a call to man shows the new extension (see Figure 17.5).

Figure 17.5. Output from the man command with the primefind custom shell command.

graphics/17fig05.jpg

You can also type man primefind to receive the complete manual page for the command (see Figure 17.6).

Figure 17.6. Manual page for the primefind command.

graphics/17fig06.jpg

The one remaining task is to make the command do what it advertises. We'll do that by implementing the startApp() method. Our implementation of startApp() consists of three logical parts:

  1. Obtain and process command arguments.

  2. Start up the prime-finding service, and wait for it to compute the results.

  3. Present the results on the shell's standard output.

We will reuse the Dispatcher class developed in Chapter 16 to distribute the computation among different peers. Recall that an instance of the Dispatcher class registers a ResultListener to report the computation's results. We will register primefind as a ResultListener: primefind blocks until Dispatcher notifies it of the results. Listing 17.4 shows the entire code for startApp() as well as resultEvent() (specified in ResultListener).

Listing 17.4 Implementing startApp() and ResultListener
 public class primefind extends ShellApp implements ResultListener { ..... //a few variables private ShellEnv enviroment = null; private Dispatcher dispatcher = null; String result = null; public int startApp(String[] argv) {      //assign the shell's enviroment      environment = getEnv();      //1. Obtain and process the command line arguments     if (argv.length != 2) {          return ShellApp.appParamError;     }     int low;     int high;     try {         low = Integer.parseInt(argv[0]);         high = Integer.parseInt(argv[1]);     } catch (NumberFormatException e) {         return ShellApp.appParamError;     }    //2. Create a new dispatcher, and submit a job to it    //   Block until resultEvent is called by Dispatcher     dispatcher = new Dispatcher(getGroup());     dispatcher.processPrimes(low, high, this);         synchronized(this) {          try {                wait();              } catch (InterruptedException me) {          }        }   //3. Process the result   //3(a) send a message with the result to the shell's standard output     Message mes = getGroup().getPipeService().createMessage();     MessageElement el = mes.newMessageElement("results", new       MimeMediaType("text/plain"), result.getBytes());     mes.addElement(el);     try {       outputPipe.send(mes);         //3(b) Create a document with the results, and assign that document         //     to an environment variable          StructuredTextDocument doc = (StructuredTextDocument)          StructuredDocumentFactory.newStructuredDocument(             new MimeMediaType("text/xml"), "result", result);          ShellObject so = new ShellObject("results", doc);          environment.add("results", so);        } catch (IOException e) {             e.printStackTrace();        }     return ShellApp.appNoError; }//end of startApp() //this implementation of ResultListener assigns the list of prime //numbers to the results variable, and notifies the blocking //thread of the available result public void resultEvent (Map resultMap) {     synchronized(this) {       result = (String)resultMap.get(ServiceConstants.RESULTSTRING);       notifyAll();     } } //end of resultEvent }//END OF primefind 

The most interesting aspect of this code is startApp()'s third part. The outputPipe variable, inherited from ShellApp, references the shell's standard output. That's just a regular JXTA OutputPipe, and we need to create a message to send through that pipe. Recall that, by default, the shell's output pipe connects to the console (command line). Thus, the message's content is displayed on the command line in this case, consisting of the comma-separated list of prime numbers.

In addition to displaying the results, the code also creates a StructuredDocument with the results, and assigns that document to a shell environment variable. You can call cat on the environment variable result to display the content of the document. You can also use exportfile to write the result document to a file.



JavaT P2P Unleashed
JavaT P2P Unleashed
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 209

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net