The Java Side of the API_Info Java/NLM Gateway
Articles and Tips: article
Senior Research Engineer
Developer Information
01 Jan 1998
A code-level description of the Java side of the Java/NLM Gateway for the API_Info application described in the first article in this issue. Topics covered include the API_Info client applet, the API_Info Java application, which are the two main parts of the Java side of the gateway.
Introduction
This article is the first companion to the "Extending Your NLM System Into Java, With Ease" article found earlier in this issue. If you haven't read that article, you may wish to read it now as it will explain the context of this one.
This article is a code-level description of the Java side of the Java/NLM Gateway for the API_Info application described in "Extending Your NLM System Into Java, With Ease." The original API_Info application was written entirely in C.
The Java side of the Java/NLM Gateway example, shown in Figure 1, consists of two main parts:
An applet to provide a GUI interface to the API_Info service
A Java application to process the client's request via gateway and service NLMs.
Figure 1: The Java/NLM gateway.
The API_Info Client Applet
An HTML document must be constructed that will tell the browser what class to load from the Web server. The following HTML code will invoke the main class of the API_Info applet which will open the window shown in Figure 2.
<applet code ="APICli.class" width=0 height=0> </applet>
Because of the Java applet security model, it is important that the HTML document, the API_Info applet's classes, and the API_Info application's classes all be put on the same Web server. Most browsers will not attempt to load classes or establish a connection to a server other than the one containing the current applet's classes.
The assumption is that whatever comes from the server specified by the URL that the user entered to obtain the applet, is probably wanted by the user. This places the responsibility for potentially hostile executables with the user.
When the user enters the URL for the API_Info HTML document, the browser connects to its server, loads the document, and then parses its HTML.
The API_Info applet probably should display something in the browser, but as you can see by the WIDTH and HEIGHT attributes in the HTML example above, it doesn't. Instead, the API_Info applet displays all GUI and information in separate windows called Frames by Java. This way users can be more selective in how they choose to display the different parts of the applet (i.e., the main window, the results window, the domain window, etc.).
The API_Info applet's main window shown in Figure 2 provides the primary interface to the user for API_Info service requests.
Figure 2: API_Info applet's main window.
The following is a list and brief description of all of the classes in the API_Info client applet:
/**************************************************************** public class APICli extends Applet contributes all applet and socket functionality class MyFrame extends Frame is the base class for all of this applet's windows class GetDomainNameFrame extends MyFrame manages a window used to select API_Info applet from a server with a different domain name class ErrorFrame MyFrame can be used as a debugger window [ like a GUI printf() ] class APIList extends MyFrame manages the: REQ_GET_LIST request to API_Info service displays a result window with a list of all record names in API_Info service's simple DB REQ_DELETE_RECORD request to have API_Info service delete the record having the specified name class APIOp extends MyFrame manages queries to API_Info service running on server REQ_GET_RECORD - displays a results window with the contents of the data field in the specified record. REQ_UPDATE_RECORD - displays a window which allows user to edit contents of the data field of a record. class APIOpNew extends APIOp manages REQ_NEW_RECORD query to API_Info service running on domain server, displays a window which allows user to enter new record name and content for the data field of the record. class APIMainWindow extends MyFrame manages main window for applet. ****************************************************************/
Most of the applet's classes above won't be discussed in this article because they are concerned with managing the applet's GUI and have nothing to do with network functionality or the gateway NLM. The two API_Info applet classes that will be explained in this article are:
APICli
GetDomainNameFrame
Note: If you want to see how the GUI classes work, you can download the project sources from our download site at the URL specified at the end of this article.
The APICli class
The APICli class inherits Java Applet functionality from the standard Java framework by extending from the framework's Applet class in line 4.
Browsers interact with applets using four routines that should be overridden in order to add functionality to a generic applet:
1. public void init( ) - called once when the applet's class is loaded.
2. public void start( ) - called each time the browser displays the applet's page.
3. public void stop( ) - called each time the browser leaves the applet's page.
4. public void destroy( ) - called once when the browser discards the applet's page.
This allows the applet to free native resources (e.g., close files, etc.). The stop( ) routine is guaranteed to be called before destroy( ). The API_Info applet does not override the destroy( ) routine because the applet keeps no native resources around between service requests.
The Java source for the APICli class is shown below:
01 /**************************************************************** 02 class APICli contributes all applet and socket functionality 03 ****************************************************************/ 04 public class APICli extends Applet 05 { 06 public static final int DEFAULT_PORT = 6790;//ServerSocket port for server side app 07 private String domainName = ""; //obtained from the browser's HTML doc connection 08 public APIMainWindow mainW ; 09 10 public void init() 11 { 12 Socket chkSock = null; 13 mainW = new APIMainWindow( this ); 14 URL docBase = getDocumentBase(); 15 domainName = docBase.getHost(); 16 try 17 { 18 chkSock = new Socket(domainName, DEFAULT_PORT ); 19 mainW.enable(domainName); // enables the buttons in the main window 20 } 21 catch(IOException e) 22 { 23 new ErrorFrame("The API_Info app is not running on server "+domainName); 24 e.printStackTrace(); 25 } 26 finally 27 { 28 if(chkSock != null) 29 { 30 try 31 { 32 chkSock.close(); 33 } 34 catch ( IOException e ) 35 { 36 e.printStackTrace(); 37 } 38 } 39 } 40 } 41 public void start() // called when browser displays our applet page 42 { 43 MyFrame.hideShowWindows(MyFrame.SHOW_WINDOW); 44 } 45 public void stop() // called when browser leaves our applet page 46 { 47 MyFrame.hideShowWindows(MyFrame.HIDE_WINDOW); 48 } 49 public String sendReceive ( String request ) //called by applet GUI for service transaction 50 { 51 Socket s = null; 52 DataInputStream sin = null; 53 PrintStream sout = null; 54 String rcvdMsg = ""; 55 try 56 { 57 s = new Socket(domainName, DEFAULT_PORT ); 58 sin = new DataInputStream ( s.getInputStream() ); 59 sout = new PrintStream ( s.getOutputStream() ); 60 sout.println( request+"\n" ); // a \n needed at end for println to work 61 sout.flush(); 62 while(true) 63 { 64 String line = sin.readLine(); 65 if( line == null || line.length() == 0 ) 66 break; 67 else 68 rcvdMsg = rcvdMsg+line+"@" ; 69 } 70 } 71 catch(IOException e) 72 { 73 e.printStackTrace(); 74 } 75 finally 76 { 77 try 78 { 79 sin.close(); 80 sout.close(); 81 s.close(); 82 } 83 catch ( IOException e ) 84 { 85 e.printStackTrace(); 86 } 87 } 88 return ( rcvdMsg ); 89 } 90 }
The main purpose of the init( ) method in APICli is to verify that a Socket connection can be obtained to the server that provided the API_Info HTML document.
In line 14, a URL object is obtained from the browser that describes the connection that it made to the server to obtain the document. In line 15, the server's domain name is extracted from the URL object with the URL object's getHost( ) method.
The domain name is important because somewhere on the network there is a domain name server which maintains a table of IP addresses associated with domain names. When the station running this applet makes a request for a connection to the server with the new domain name, the domain name server returns the associated IP address to the station so that the station can make the connection.
In line 18, a Socket connection to a port on the server with the domainName is attempted. This port number must agree with the port number used on the server side by the API_Info Java application. If the application is running on the server, the connection will be successful and the main window is told to enable its buttons allowing the user to proceed with service requests.
If the connection is unsuccessful, an IOException will occur terminating execution in the try block before enabling the buttons in the main window and transferring control to the catch block.
Java finally blocks are always associated with try blocks. The init( ) routine's final block is associated with the try for the Socket connection above it. Regardless of whether the try is successful or has an exception, the final block is guaranteed to run. The final block in the init( ) routine, closes the Socket connection, if one was obtained.
APICli's sendReceive routine is used by the applet's GUI classes to send requests to the service and receive responses from it. The user can employ the GUI to request these transactions only if the main window's buttons have been enabled indicating that the target server's setup is good.
For each request, sendReceive attempts to create an input and output stream through a Socket connection to the API_Info application running on the server with the validated domainName. The output stream's println( ) method is used to send the request to the application which forwards it to the Gateway NLM (explained in a moment). The flush( ) method forces any data that may be buffered locally out over the wire.
After the request is sent, the program enters a forever loop that receives data from the input stream in the form of Java Strings. Each of these strings are concatenated with an '@' character that is recognized by the GUI as a delimiter. The received strings are concatenated together to form one large buffer in a String. The loop is terminated either by receiving an empty string or by an exception.
Whether successful or unsuccessful, sendReceive's final block is run to close the streams and the socket.
Finally, sendReceive returns the received buffer to the API_Info applet GUI object that called it.
The GetDomainNameFrame class
A user can click the Domain button on the main window if they wish to connect to a different server and potentially a different database. The Domain Window shown in Figure 3, is managed by the API_Info applet's GetDomainNameFrame class. The setup for the new domain must be the same as it was for the first; that is, the HTML document, applet classes, and application classes must be located on the new server and the application must be running in the new server's JVM.
Figure 3: The API_Info Applet Domain window.
The Java source for the GetDomainNameFrame class is shown below:
/**************************************************************** class GetDomainNameFrame manages a window used to select an API_Info applet from a server with a different domain name ****************************************************************/ class GetDomainNameFrame extends MyFrame { public final static String HTM_FILE_NM = "APICli.htm"; public final static String HTTP_HDR = "http://"; public final static String PROMPT = "Enter server's domain name below:"; private Button setDN; private Button close; private TextField dN; public GetDomainNameFrame( APICli theApiCli ) { super( "Enter Domain Name" , theApiCli ); showWindow( ); } public void showWindow( ) { Label prompt = new Label( PROMPT , Label.LEFT ); this.add("North",prompt); dN = new TextField( "",80 ); dN.setBackground(Color.white); this.add("Center",dN); setDN = addButton( "Set Domain" ); close = addButton( "Cancel" ); super.showWindow(dN); } public void checkDomainNm( ) { String domName = dN.getText(); if( domName.charAt( domName.length() -1 ) != '/' ) domName = domName + "/"; String uRLString = HTTP_HDR + domName + HTM_FILE_NM ; URL docBase = apiCli.getDocumentBase(); try { URL newDocBase = new URL( docBase , uRLString ); AppletContext appletContext = apiCli.getAppletContext(); appletContext.showDocument(newDocBase); } catch(MalformedURLException e) { e.printStackTrace(); } } public boolean handleEvent( Event evt ) { switch( evt.id ) { case evt.ACTION_EVENT: if ( evt.target == close) { this.hide(); this.dispose(); return (true); } else if ( evt.target == setDN) { try { checkDomainNm( ); } catch(MalformedURLException e) { e.printStackTrace(); return (true); } this.hide(); this.dispose(); return (true); } } return super.handleEvent( evt); } }
Most of the methods in this class are exclusively concerned with the GUI and so won't be discussed. CheckDomainNM( ) is the only method that is pertinent to network communication. The purpose of CheckDomainNm( ) is to interact with the browser to obtain a new API_Info applet from the server specified by the domain name entered by the user.
The first thing that CheckDomainNm does is to obtain the name entered by the user into the Domain Window's text entry field and convert it into a URL string.
Next, an attempt is made to have the browser obtain the API_Info HTML document page from the server with the new domain name. This document will be identical to the one described earlier in this article.
In order to load the new HTML document, a URL object describing the connection used to obtain the current API_Info applet is obtained and used to create a URL object describing the location of the HTML document on the new server.
A Java AppletContext object describes and provides hooks into the browser running the applet. Since APICli extends the standard Applet class, it inherits the ability to call getAppletContext( ) to obtain an AppletContext.
Using the obtained AppletContext's showDocument( ) method, CheckDomainNm( ) tells the browser to obtain the HTML document specified by the new URL. The HTML document obtained at this location will then cause the browser to load the API_Info applet classes from the new domain server. The newly loaded applet classes will then attempt to connect to the API_Info Java application running on its server and operate as described in the APICli class explanation above.
The API_Info Java Application
The purpose of the API_Info Java application is to listen for client applet connections and spawn a thread for each one that it receives. Using the client thread, the client service request is forwarded to the API_Info Gateway and Service NLMs for servicing.
The are two API_Info application classes in the API_Info application:
APIApp
APIConnection
The APIApp class
The APIApp class inherits Java Thread functionality from the standard Java framework by extending from the framework's Thread class.
The Java source for the APIApp class is shown below:
/***************************************************** class APIApp provides main for application also monitors a ServerSocket *****************************************************/ public class APIApp extends Thread { public final static int DEFAULT_PORT = 6790 ; protected ServerSocket listen_socket ; static APIApp apiApp; static { System.loadLibrary("NMGate"); } public static void main ( String[] args ) { apiApp = new APIApp( ); } public APIApp ( ) { try { listen_socket = new ServerSocket(DEFAULT_PORT ); } catch (IOException e) { fail ( e ); } System.out.println ("APIApp:listening on port "+DEFAULT_PORT ); this.start(); } public void run() { try { while(true) { Socket client_socket = listen_socket.accept( ); APIConnection c = new APIConnection( client_socket , this ); } } catch ( IOException e ) { fail ( e ); } } public static void fail ( Exception e ) { e.printStackTrace(); System.exit ( 1 ); } }
APIApp has a static initializer. Java static initializer methods are run automatically when their classes are loaded. Since, at class load time, there are no instances of the class, there is no significant data to pass as a parameter; nor is there an object in existence to receive a return value. So, static initializer methods by definition can have neither arguments nor a return value. Also, since the system calls initializer methods automatically, they don't require names.
APIApp uses its static initializer to load the NMGate native library. Since the only current native implementation of API_Info server side functionality is provided by the API_Info gateway and service NLMs, the API_Info Java application can currently only be run on an IntranetWare server.
However, if there were a native code library available for another platform that provided the same functionality as the API_Info gateway and service NLMs, the API_Info Java application could be run on that platform as well, with no changes to its code.
Like C applications, all Java applications must have a main( ). The API_Info Java application's main( ) creates an instance of the APIApp class causing the APIApp constructor to be invoked. The APIApp constructor creates a standard ServerSocket from Java's net package to monitor the port designated for API_Info applet connections.
If the ServerSocket instantiation is successful, APIApp calls the Java Thread method start( ) to cause the Java thread management classes to spawn a server thread to monitor the port for connection requests. After the Java thread management classes have created and initialized the thread, they will call the Thread's run( ) method. Since APIApp has extended the run( ) method, the APIApp run( ) method is the one invoked.
APIApp's run( ) method enters a forever loop that will only be ended upon termination of the application. This loop blocks on ServerSocket's accept method until a client connection is received, at which point an instance of the APIConnection class is created. As you will see in the following discussion, APIConnection also extends Thread, giving each client connection its own thread of execution.
The APIConnection class
An APIConnection object is created for each client connection that is received by APIApp's ServerSocket. Since APIConnection is derived from the Thread class, that means that each client connection is given its own thread. The purpose of APIConnection is to initialize this thread and use it to pass the client request outside of the JVM to the API_Info GateWay NLM.
The Java source for the APIConnection class is shown below:
class APIConnection extends Thread { public static final String SRVR_ERROR_STR = "ERROR:" ; public static final int NOT_FOUND = -1 ; protected Socket client ; protected DataInputStream in; protected PrintStream out ; protected APIApp server; public APIConnection( Socket client_socket , APIApp theServer ) { server = theServer; client = client_socket; try { in = new DataInputStream ( client.getInputStream() ); out = new PrintStream ( client.getOutputStream () ); } catch( IOException e ) { System.out.println ( "Exception while getting socket streams:"+e); e.printStackTrace(); try { client.close(); } catch(IOException e2 ) { System.out.println ( "Error while attempting to close"); } return; } this.start(); } public void sendReplyAndFlush ( String sendStr ) { out.println( sendStr ); out.flush(); } public void run () { try { String requestData = ""; while(true) { String line = in.readLine(); if( line == null || line.length() < 1 ) break; else requestData = requestData+line+"\n";// readLine deletes newlines } if( requestData.equals("") || requestData.length() < 2 ) sendReplyAndFlush (SRVR_ERROR_STR+"No valid request code" ); else { int chopNewLineIndex = requestData.lastIndexOf("\n"); requestData = requestData.substring(0,chopNewLineIndex); try { char digChar = requestData.charAt(0); if(! Character.isDigit(digChar)) sendReplyAndFlush (SRVR_ERROR_STR+"No valid request code" ); else { String reqStr = requestData.substring(0,1); try { int request = Integer.parseInt(reqStr); String reqData = requestData.substring(2); String replyData = NMGate( request, reqData ); sendReplyAndFlush (replyData ); } catch( NumberFormatException c) { sendReplyAndFlush (SRVR_ERROR_STR+"No valid request code" ); } } } catch(StringIndexOutOfBoundsException b) { sendReplyAndFlush (SRVR_ERROR_STR+"No valid request code" ); } } } catch(IOException a) { a.printStackTrace(); } finally { try { client.close(); this.stop(); } catch (IOException d ) { d.printStackTrace(); } } } public native String NMGate( int request , String reqData ); }
The first action taken by APIConnection is to instantiate an input stream and an output stream for the received client socket connection. APIConnection then calls the Java Thread method start( ) to cause the Java thread management classes to spawn a thread.
After the Java thread management classes have created and initialized the thread, they will call the Thread's run( ) method. Since APIConnection has extended the run( ) method, the APIConnection run( ) method is the one invoked.
In the run( ) method, APIConnection first enters a forever loop that receives data from the input stream in the form of Java Strings. The received strings are concatenated together to form one large request buffer in a String. The loop is terminated either by receiving an empty string or by an exception.
Next, run( ) performs some API_Info specific validity tests and formatting operations on the buffer before calling the NMGate native method.
The NMGate native method is located in the GateWay NLM referred to earlier. Its purpose is to serve as a translator between the Java world and the NLM world. The API_Info Java application and the API_Info GateWay NLM use the Java Native Method Interface to bind the method into Java, thereby extending the native functionality of the JVM to include the API_Info service.
When the gateway NLM's routine is called, control is passed to the routine on the same client thread spawned for the client by the Java application in the JVM.
The gateway routine simply passes the request directly to the Service NLM's service routine, which returns a response buffer to the gateway, which returns it to the application, which sends it back to the client applet over the client's connection and the thread is terminated.
Notes:
The Java Native Interface package or JNI is part of Java version 1.1, so the Web Server must be Java 1.1 enabled for this application to run. You can download your IntranetWare/Java SDK which includes the Java 1.1 NLM for your Web server from http://developer.novell.com/java/sdk.
The native method in the gateway NLM must be defined with the public and native keywords in order for the native binding to occur when the application is loaded. Most of the work to implement a Java native method is done on the native side, in this case in the gateway NLM. For a code-level description of how to implement and build the gateway NLM, refer to the article "The NLM Side of the API_Info Java/NLM Gateway Example," in the next issue.
Our Download Site
You can use the URL shown below to access our download site for all sources found in subsequent issues of this publication, including the sources for the C-based API_Info application and the API_Info Java/NLM gateway system.
http://www.novell.com/research
Disclaimer
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.