Received: from SOUTH-STATION-ANNEX.MIT.EDU by po10.MIT.EDU (5.61/4.7) id AA02104; Tue, 14 Dec 99 20:17:36 EST
Received: from hermes.javasoft.com by MIT.EDU with SMTP
	id AA03977; Tue, 14 Dec 99 20:17:17 EST
Received: (from nobody@localhost)
	by hermes.java.sun.com (8.9.3+Sun/8.9.1) id BAA16316;
	Wed, 15 Dec 1999 01:16:49 GMT
Date: Wed, 15 Dec 1999 01:16:49 GMT
Message-Id: <199912150116.BAA16316@hermes.java.sun.com>
X-Authentication-Warning: hermes.java.sun.com: Processed from queue /bulkmail/data/ed_24/mqueue4
X-Mailing: 193
From: JDCTechTips@sun.com
Subject: JDC Tech Tips  December 14, 1999
To: JDCMember@sun.com
Reply-To: JDCTechTips@sun.com
Errors-To: bounced_mail@hermes.java.sun.com
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
X-Mailer: Beyond Email 2.2


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
December 14, 1999. This issue covers using remote method 
invocation to access legacy databases.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

USING REMOTE METHOD INVOCATION TO ACCESS LEGACY DATABASES

Remote Method Invocation (RMI) is a way to communicate between
two Java(tm) programming language applications that are remote 
from each other in a network.

RMI differs from lower-level communication mechanisms like 
sockets in a couple of important areas. One is that you can use 
RMI to do remote method calls in a natural way. That is, you can 
obtain a reference to an object on a remote server and then 
make method calls on the object. You can also do remote method 
calls with sockets, but you have to set up your own arrangements 
for argument passing, return value passing, and so on; this 
approach isn't integrated into the Java programming language 
very well. RMI takes care of these details for you.

Another difference is that RMI requires Java support. If you have 
a server and a client, and they communicate using RMI, then
both need to be implemented as Java programs. This is because of
the underlying Java facilities used to implement RMI, such as 
serialization. It's also because of the Java language semantics 
implied by RMI, such as interfaces.

But the requirement that you use Java on both ends doesn't prevent 
you from using RMI with other languages. And that's one of the 
things that this tip illustrates. The tip presents a client/server 
RMI-based application that accesses a legacy database via C and 
C++ functions. Here "legacy" means an older application, one that 
isn't necessarily written using Java programming or that is 
network aware.

So this is a fairly complex application, one where you have a:

o Database
o C function that accesses a database
o C++ wrapper function that implements a Java native method,
  and calls a C function
o Server that creates a remote object and registers it
  with an RMI registry
o Client that looks up the remote object reference via the 
  registry. The client makes a method call on the object to access 
  the database through the native method wrapper and the C 
  function.
  
The RMI registry, server, and client all run on the same machine, 
but the tip fully illustrates areas like security and codebases.

One important thing to note about this tip...
although the tip demonstrates a working RMI application, it is not a
tutorial on RMI. The last section of the tip points to documentation
that you read to learn more about RMI.  

How This Tip is Organized

Because the application is fairly complex, this tip is longer
than the typical JDC Tech Tip. To make it easier to follow, the tip
is organized in three parts:

Part 1. Developing the Application. This part defines the pieces
that comprise the application. The tip provides source code for 
each of these pieces.

Part 2. Putting it all Together. This part describes how to put
all the pieces together into a working application.

Part 3. Running the application. This part describes the actions 
you need to take to run the application.


Part 1. Developing the Application

Developing an RMI application involves steps such as defining an
interface for remote objects and implementing the interface. This
part of the tip describes how to develop this particular RMI
application, that is, one that accesses a remote database through 
C and C++ functions.

1. Define an Interface For Remote Objects

   The first step in developing an RMI application is to define 
   an interface that remotely-called objects must implement. 
   In this application, the interface is called Search, and it 
   is in a package called rmitest:

       // Search.java

       package rmitest;

       import java.rmi.Remote;
       import java.rmi.RemoteException;

       public interface Search extends Remote {
           public String findEntry(String w) throws RemoteException;
       }
  
   Search declares a single method findEntry, that is used to 
   look up an entry in a database (such as a phone or address 
   list), and return the entry that it found. In other words, 
   the server application registers (with an RMI registry) an 
   instance of a class that implements the Search interface. 
   A client can then consult the registry to obtain a reference 
   to the instance's stub. The client can call methods via the 
   stub to effect database lookups. A further discussion of stubs 
   is found later in the tip.

   An interface that describes remote class functionality must 
   extend the java.rmi.Remote interface. Also, methods must 
   declare that they throw java.rmi.RemoteException. 
   RemoteException is used to handle the special types of failures 
   that can occur in RMI, such as a network failure.
   

2. Define an RMI Remote Object That Implements the Interface

   After the interface for a remote class has been defined, you need
   to implement the interface, that is, define a remote class. In 
   this example, the class is called SearchImpl:

       // SearchImpl.java

       package rmitest;

       import java.rmi.server.UnicastRemoteObject;
       import java.rmi.RemoteException;

       public class SearchImpl extends UnicastRemoteObject
           implements Search {

           // load C/C++ shared library

           static {
               System.loadLibrary("rmilib");
           }

           // constructor

           public SearchImpl() throws RemoteException {
               super();
           }

           // public remotely-callable method that finds an entry

           public String findEntry(String w) {
               return findEntry0(w);
           }

           // native C++ method to actually look up an entry

           private native static String findEntry0(String w);
       }

   All this class does is load a shared library, that contains the 
   C and C++ functions used to search the database. Calls to 
   findEntry are simply passed through to a native method 
   findEntry0.

   SearchImpl extends a class java.rmi.server.UnicastRemoteObject. 
   This class provides some of the basic RMI functionality, such as
   support for remote object references.

3. Define a C Function That Searches a Database

   The C part of this application is a function that accesses some 
   type of legacy database. For this application, a function called
   "func" searches a text file for a string. It then returns the
   first line of the file that contains the string. You can use
   a function like this to look up names in a phone directory. The 
   function consults a text file "data.txt", which is the database.

       /* func.c */

       #include <stdio.h>
       #include <stddef.h>
       #include <string.h>

       /* database */

       char* db = "data.txt";

       /* process input -> output */

       void func(char* in, char* out) {
           char inbuf[256];
           int found = 0;
           FILE* fp;

           /* input string is empty */

           if (in == NULL || !strcmp(in, "")) {
               strcpy(out, "*** not found ***");
               return;
           }

           /* open database */

           fp = fopen(db, "r");
           if (fp == NULL) {
               strcpy(out, "*** database open error ***");
               return;
           }

           /* search database and return first matching line */

           while (fgets(inbuf, sizeof inbuf, fp) != NULL) {
               if (strstr(inbuf, in) != NULL) {
                   found = 1;
                   break;
               }
           }
           fclose(fp);

           /* found an entry, clip off newline */

           if (found) {
               size_t len = strlen(inbuf);
               if (len >= 1 && inbuf[len - 1] == '\n')
                   inbuf[len - 1] = 0;
               strcpy(out, inbuf);
           }
           else {
               strcpy(out, "*** not found ***");
           }
       }

4. Define a C++ Wrapper / Java Native Method For the C Function

   After you define the C function, you define a C++ wrapper
   function for the C function. You might ask "why not use the
   C function directly as a native method callable from Java?" 
   The reason for the C++ wrapper is that native methods 
   implemented in C/C++ require a particular name or signature.
   If you're accessing legacy functions and data, you may not be 
   able to change the name of existing functions. So another 
   function is defined as a wrapper around the legacy function.

   This function has a name:

       Java_rmitest_SearchImpl_findEntry0

   that corresponds to a Java native method:

       rmitest.SearchImpl.findEntry0

   How do you know what name to give this wrapper function?  You can
   use the "javah" tool to generate a declaration for the wrapper, 
   by saying:

       javah -jni -o rmilib.cpp rmitest.SearchImpl

   This command generates a Java(tm) Native Interface (JNI) 
   declaration for a native method. You don't need to use the 
   javah tool in this application because the declaration of the 
   native method, and its implementation, are provided here:
   
       // rmilib.cpp

       #include <stdio.h>
       #include <string.h>
       #include <jni.h>

       // C function that searches database

       extern "C" void func(char*, char*);

       // declaration for native method rmitest.SearchImpl.findEntry0()

       extern "C" {

       JNIEXPORT jstring JNICALL Java_rmitest_SearchImpl_findEntry0
           (JNIEnv *env, jclass, jstring str) {
           char inbuf[256];
           char outbuf[256];

           // get the input string

           const char* s = env->GetStringUTFChars(str, NULL);
           if (s == NULL)
               return NULL;

           // copy it out to a char buffer

           strcpy(inbuf, s);
           env->ReleaseStringUTFChars(str, s);

           // call C function

           func(inbuf, outbuf);

           // format output for return

           return env->NewStringUTF(outbuf);
       }

       }

   This wrapper takes an input string and converts it to a 
   C-style string (a sequence of bytes terminated by a null byte).
   The wrapper then calls the C legacy function and formats the 
   string for return to the calling Java program.

   After the two C/C++ functions are compiled, they are grouped 
   in a shared library. This makes the functions callable from
   a Java application. 

5. Define a Server Program

   So far you've defined a remote interface and class, and a couple 
   of C and C++ functions that issue remote object calls. But how 
   do you make a remote object "do" something?

   The first step is to register a remote object instance. This 
   means you create a remote object in a server and then call 
   java.rmi.Naming.rebind to associate that object with a name.
   This process uses a registry program that can remember the 
   name-object association. If an object is registered, a client 
   program can call java.rmi.Naming.lookup with the associated name;
   lookup will then return the registered object. More precisely,
   lookup returns a stub to the remote object. Stubs are described 
   in the section on clients later in the tip.

   The Java(tm) Development Kit has a program called "rmiregistry" 
   that you use as a registry. You start this program before 
   starting the RMI server and client. The rmiregistry program
   remembers name-object associations specified by the RMI server 
   program. This allows an RMI client program to look up an 
   association by name, and obtain a stub reference to a remote 
   object. Here is the server program for the application.

       // Server.java

       package rmitest;

       import java.rmi.Naming;

       public class Server {
           public static void main(String args[]) {

               // install RMI security manager

               System.setSecurityManager(new SecurityManager());

               // create a remote object and register it

               try {
                   SearchImpl si = new SearchImpl();
                   Naming.rebind("searchobj", si);
               }
               catch (Exception e) {
                   System.err.println(e);
               }
           }
       }
       
   Notice that the client program creates a remote object and
   registers it using the name "searchobj". It also installs
   a security manager. (The server program will need to do this 
   too.) Without a security manager, the RMI class loader will 
   not download classes from remote locations. The security 
   manager protects against malicious operations by loaded 
   classes.

6. Define a Client Program That Calls the Server

   The final piece you need to define is a client program. This
   program takes an input string that you specify and uses it
   to look up information in the database. Specifically, the
   client issues RMI calls to the findEntry method of the remote 
   object that the server program registered.

   Recall that the server program registers a remote object using
   the name "searchobj". The client program uses Naming.lookup to
   look up the remote object; it also specifies the host name 
   where the server is running:
   
       //localhost/searchobj

   The name "localhost" is a special name for the local machine 
   with IP address 127.0.0.1 (another name for this is the 
   "loopback" address). In this example, both the client and the 
   server are running on the same machine. If you run the server 
   on a different host, you would replace "localhost" with that 
   host name.
   
   Here is the client program for the application.

       // Client.java

       package rmitest;

       import java.rmi.Naming;
       import java.awt.*;
       import java.awt.event.*;
       import javax.swing.*;

       public class Client {

           // do call on remote object

           public static String lookup(String str) {
               String t = null;
               try {
                   String host = "//localhost/searchobj";
                   Search s = (Search)Naming.lookup(host);
                   t = s.findEntry(str);
               }
               catch (Exception e) {
                   System.err.println(e);
               }
               return t;
           }

           public static void main(String args[]) {
               JFrame frame = new JFrame("RMI Client");
               frame.addWindowListener(new WindowAdapter() {
                   public void windowClosing(WindowEvent e) {
                       System.exit(0);
                   }
               });

               // set RMI security manager

               System.setSecurityManager(new SecurityManager());

               // set up input and output areas

               final JTextField field = new JTextField(25);
               field.requestFocus();
               final JLabel label = new JLabel(" ");

               // process input

               field.addActionListener(new ActionListener() {
                   public void actionPerformed(ActionEvent e) {
                       label.setText(lookup(field.getText()));
                       field.requestFocus();
                   }
               });

               // set up panels and so forth

               JPanel panel1 = new JPanel();
               JPanel panel2 = new JPanel();
               panel1.add(field);
               panel2.add(label);
               frame.getContentPane().add("North", panel1);
               frame.getContentPane().add("South", panel2);
               frame.pack();
               frame.setVisible(true);
           }
       }

   The heart of this code is the three lines:

       String host = "//localhost/searchobj";
       Search s = (Search)Naming.lookup(host);
       t = s.findEntry(str);

   The first two of these look up a remote object by name
   ("searchobj"). The last line does the database lookup using 
   the remote object's stub.

   You might wonder whether the client "really" is operating on
   the remote object. In other words, does the client have local 
   to itself the actual remote object? The answer is "no", and 
   this gets at the heart of what's going on inside of RMI.

   In Part 2 of this tip, "Putting it All Together," you will 
   run a tool called the RMI compiler (rmic). This tool generates 
   a "stub"  for the SearchImpl class. The stub exists on the 
   server, but can be dynamically downloaded to the client. So 
   when you run the client, it interacts with the remote object 
   via the stub. It's the stub (not the object) that is responsible 
   for formatting and transmitting method arguments ("marshalling") 
   to the RMI system on the server. A "skeleton" class on the 
   server side "unmarshals" this information and makes the actual 
   call on the remote object. The process is reversed to transmit 
   return values back to the client.

   In this example, the client side has available locally the class
   files Client.class and Search.class. The stub class
   SearchImpl_Stub.class is downloaded on demand from the server. 
   The actual remote class SearchImpl.class is not downloaded to the
   client. So the remote object stays on the server, and the client
   interacts with it via the stub class instance.

   Another thing to keep in mind is the concept of a "codebase." 
   This is where stubs are stored. In this application, a codebase 
   is specified when you run the server. The specification looks 
   like this:

           -Djava.rmi.server.codebase=http://localhost:2001/

   When you register a remote object using Naming.rebind, a 
   codebase for the object is recorded. When the object's stub 
   needs to be downloaded, it can be found using the codebase. 
   In this application, a simple HTTP server is used to serve up
   stub .class files from the codebase. This server uses port 2001 
   on localhost.

   Codebases have some relation to CLASSPATH settings. When
   rmiregistry tries to find a file, it looks at the CLASSPATH 
   first and then the codebase. This particular application is 
   configured so that the codebase settings must be observed for 
   the application to work.

   Notice that like the client program, the server program installs
   a security manager to protect against malicious operations by 
   loaded classes.
   

Part 2. Putting It All Together

Here's how to take the various pieces of the application that you
defined and assemble them into a working application:

1. Create a base directory. This tip uses the name
   base. The directory might actually be something like:

    /usr/jones/rmibase

   on UNIX, or:

    c:\rmibase

   on Windows. If you're using Windows, replace all / with \, but
   leave http:// paths alone.

2. Create a directory structure under base:

    base/client
    base/client/rmitest
    base/server
    base/server/rmitest
    base/src
    base/examples
    base/examples/classServer

3. This tip assumes that JDK 1.2.x is installed in:

    /jdkbase

4. Copy all of the above source files to base/src.

5. Change directories to base/src, and compile the C legacy
   function by saying:

    cc -c func.c

   This compiles func.c to an object file (func.obj for Win32, or
   func.o for Solaris).

   If you're using a Windows C/C++ compiler like Borland C++, you 
   might say:

    bcc32 -c -P- func.c

   func.c is a C function, not a C++ one, so use appropriate 
   compiler options to specify C compilation.

6. Compile the C++ native function by saying:

    c++ -c -I/jdkbase/include -I/jdkbase/include/win32 rmilib.cpp

   where "c++" is your local C++ compiler. If you're using Solaris,
   replace "win32" with "solaris". This step also produces an object
   file (rmilib.obj or rmilib.o).

7. Create a shared library by saying:

    bcc32 -tWD rmilib.obj func.obj

   With Solaris you say:

    cc -G -o librmilib.so rmilib.o func.o

   In other words, you combine the two object files into a shared
   library.

   You should now have a library rmilib.dll (Win32) or 
   librmilib.so (Solaris). Move this library to base/server. Note 
   that there's a platform-dependent mapping between the library 
   name specified to System.loadLibrary and an actual shared 
   library name on your system. For example, loading a library 
   named "abc" implies a name "abc.dll" for Win32, and "libabc.so" 
   for Solaris.

   This application assumes that the current directory (".") is 
   in the search path for loading shared libraries into a Java 
   program. If you have trouble with this area, you might wish to 
   change your "java.library.path" setting. This setting is forced 
   in the server invocation below.

8. In base/server create a small text file called data.txt, with
   lines like this:

    Jane Jones      457-9231
    Tom Garcia      143-5876
    Bill Smith      456-8918

   Separate the fields of each line with spaces. This is the 
   legacy database that will be searched by the application.

9. Change directories to base/src, and say:

    javac Search.java SearchImpl.java Server.java Client.java

10. Copy:

    Search.class SearchImpl.class Server.class

    to base/server/rmitest.

11. Copy:

    Search.class Client.class Client$1.class Client$2.class

    to base/client/rmitest.

12. Change directories to base/server, and say:

    rmic -classpath . -d . rmitest.SearchImpl

    This step generates the SearchImpl_Skel.class and
    SearchImpl_Stub.class files.


Part 3. Running the Application

Here's what actions you need to take to run the application.

1. Create a desktop window and say:

    rmiregistry

   Do this from a directory positioned such that server/rmitest 
   is not reachable via your CLASSPATH. In other words, start 
   rmiregistry from somewhere unrelated to this application's 
   source and .class files. This is done to avoid confusing 
   CLASSPATH and codebase locations when searching for stub files.

   The rmiregistry program doesn't display or print anything. It 
   just waits for registry requests.

2. Download the class server from:

    ftp://ftp.javasoft.com/pub/jdk1.1/rmi/class-server.zip

   This is a small download (5K). Unzip the files into the 
   directory base/examples/classServer.

   Change directories to base/examples/classServer and say:

    javac ClassFileServer.java ClassServer.java

3. Run the class server in a new window by changing directories to
   base and saying:

    java examples.classServer.ClassFileServer 2001 base/server

   2001 is the class server's port, and base/server is where the
   server looks for .class files. So the server will look for
   SearchImpl_Stub.class with the pathname
   base/server/rmitest/SearchImpl_Stub.class.

4. Run the RMI server in a window by changing directories to
   base/server and saying:

    java \
        -Djava.library.path="." \
        -Djava.security.policy=base/server/java.policy.server \
        -Djava.rmi.server.codebase=http://localhost:2001/ \
        rmitest.Server

   java.policy.server is a policy file in base/server. It's a text 
   file that should contain these lines:

    grant {
        permission java.lang.RuntimePermission "loadLibrary.*";
        permission java.util.PropertyPermission "user.dir", "read";
        permission java.net.SocketPermission
            "*:1024-65535", "connect,accept";
    };

   The policy file specifies permissions used by the security 
   manager installed in the server.

5. Run the client in a window by changing directories to 
   base/client and saying:

    java \
        -Djava.security.policy=base/client/java.policy.client \
        rmitest.Client

   java.policy.client is a policy file in base/client. It's a text
   file that should contain these lines:

    grant {
        permission java.net.SocketPermission
            "*:1024-65535", "connect,accept";
    };

6. Enter a string into the input area of the client GUI. The 
   application will use the string to search the legacy database.
   It will return and display the first line in the database that 
   contains a match.

Further Information

There are a number of RMI papers available online. These four
provide basic information about RMI, describe codebases,
and answer common questions:

    http://java.sun.com/products/jdk/rmi/
    http://java.sun.com/products/jdk/1.2/docs/guide/rmi/getstart.doc.html
    http://java.sun.com/products/jdk/1.2/docs/guide/rmi/faq.html
    http://java.sun.com/products/jdk/1.2/docs/guide/rmi/codebase.html

This paper describes how to use the Java Native Interface to
combine Java and C/C++ code:

    http://java.sun.com/docs/books/tutorial/native1.1/

   
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  

- NOTE
The names on the JDC mailing list are used for internal Sun
Microsystems(tm) purposes only.  To remove your name from the 
list, see Subscribe/Unsubscribe below.


- FEEDBACK
Comments?  Send your feedback on the JDC Tech Tips to:

jdc-webmaster@java.sun.com


- SUBSCRIBE/UNSUBSCRIBE
The JDC Tech Tips are sent to you because you elected to 
subscribe when you registered as a JDC member.  To unsubscribe 
from JDC Email, go to the following address and enter the email 
address you wish to remove from the mailing list:

http://developer.java.sun.com/unsubscribe.html


To become a JDC member and subscribe to this newsletter go to:

http://java.sun.com/jdc/


- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://developer.java.sun.com/developer/TechTips/index.html


- COPYRIGHT
Copyright 1999 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://developer.java.sun.com/developer/copyright.html


This issue of the JDC Tech Tips is written by Glen McCluskey.

JDC Tech Tips 
December 14, 1999












