Make your development of RMI-based applications much easier -- use an in-process server to develop and test your client/server code Developers with experience using RMI for product development largely agree that debugging client/server code presents many difficulties. Stepping into the server from the client or vice versa is not fun for many reasons. Debugging client/server programs using the RMI API is particularly painful, because the execution path jumps back and forth between client and server.This article presents a simple and elegant solution to the many problems associated with RMI-based product development. The article assumes you have a reasonably good understanding of RMI-based client/server development.Problems in debuggingRunning two debuggers on one machine requires us to have reasonably high-end machines. And switching between two applications is always a nuisance. If we run the debuggers on two different machines, then we need to use two keyboards to step through the debugging process. These petty debugging logistics distract the developer from problem solving. A common source of trouble is running RMI-based applications on a computer or laptop that is not configured adequately for TCP/IP. This is a big problem for developers who prefer to use their laptops or home PC for product development. Most people use modems to connect to the Internet — and their TCP/IP is configured to obtain a dynamic address each time they connect to their ISP. So, whenever an RMI application is run, the dial-up dialog box comes up to put the computer on a valid network. To avoid this problem, we can tweak the TCP/IP and COM port settings so that we can run RMI applications without connecting to the ISP — but then we cannot connect to the Internet while we are developing RMI applications. Reconfiguring the computer (or laptop) frequently is not a feasible solution. As a whole, working from home is not an easy option for those using RMI.This article describes a simple design technique that allows us to debug the server and client in a single process, without breaking any RMI guidelines or the existing code base. It describes how RMI can be bypassed entirely when you want to debug the client and server code in any debugger in a traditional style (within a single process). The same codebase can be used to run the client and server in separate processes or in a single process, merely by toggling a boolean flag in the client.Benefits from this design techniqueYou can implement this technique for your existing codebase in half a day or in a full day, depending on the number of remote interfaces your server supports — and depending on your typing speed. Once everything is in place, developers can reap the following benefits: Code can be written and tested on a single machine, in a single process.There is no need to have a network card and a valid TCP/IP address.Any JDK 1.1-compatible debugger can be used for debugging both client and server.Development can be done at home without disturbing the ISP settings. There is no need to get online to obtain a dynamic IP address.RMI-based products can be demonstrated on a laptop without changing any network or TCP/IP settings.While the server application is running, a modem can be used to get on the Internet at will.There is no need to run an RMI registry at the time of debugging.This design technique requires us to implement the server to support two modes of execution. These modes are:Remote (standalone) server modeIn-process server modeRemote (standalone) server modeIn this mode, the server process will be kicked off on a server machine using a simple startup program. This startup program creates a server object and binds it to the registry so that clients can do a lookup through Naming.lookup(). If the server object is creating one or more server-side proxy objects that support remote interfaces, then the server takes the responsibility of registering them with the local RMI registry. Any RMI-based server supports this mode by default.In-process server modeIn the second mode, the server assumes that it is running inside the client’s process. An in-process server is functionally a subset of the remote server. In this mode, the server assumes that the client has direct access to all remote interfaces supported by the server. This is possible because the client and server are sharing the same process space. In this mode, server objects (server proxies) that implement remote interfaces need not be registered with the local RMI registry. Also, the server avoids invoking the Naming.bind() and Naming.rebind() methods. In addition, it avoids making calls to the API defined in java.net.* classes because the network API methods are not required for setting up communication between client and server. However, the server does not care how the client gets a direct reference to its remote interfaces. The server simply makes sure that all of its services are always available only through remote interfaces. This is precisely the design philosophy of the RMI protocol — so an in-process server does not break any RMI guidelines.How does a client communicate with the in-process server?The client implementation has to be split into two pieces to be able to deploy the design technique described above. The first part of the client code will isolate the RMI-based code from the client implementation. It obtains the remote interfaces on the server using either RMI or through an in-process server. The second part of the client code accepts these remote interfaces to invoke the remote API on the server.Step-by-step implementation of client/serverHere is a summary of changes required to the client and server code: The server should be modified to support two modes of execution (if required)The client should be split into two parts: The first part acquires a remote server interfaceThe second part accepts the server interface and invokes the client applicationThe following sections describe these steps in more detail using a sample application.If your server satisfies the following conditions, then the server can be run as an in-process server without modifying a single line of server code, and you can skip ahead to Step 2. Otherwise, continue with Step 1 below.Condition 1: Server does not create any remote objects that implement remote interfaces Condition 2: Server does not use API defined in java.net.* classesStep 1: Modifications to the server codeThe Server object extends from UnicastRemoteObject and implements ServerInterface. Add a constructor to Server that takes a boolean flag to indicate the mode in which this server has to execute. If the in-process boolean flag is set to “true,” then this server simply ignores all activity related to RMI. It will not register any proxies and will ask all its aggregates to follow suit. By default this flag is initialized to “false.” The following code shows a simple constructor required on the Server object to support the in-process server mode. //Non-Default constructor to handle in-process server mode. public Server(String sWorkingDir, boolean bRunAsInProcessServer) { m_sWorkingDir = sWorkingDir; bRunAsInProcessServer = bRunAsInProcessServer; } Step 1-A: Handle server-side proxiesIf your server registers one or more remote objects to be used by clients, skip all RMI activity if the in-process flag is set to “true.” Insert an if() block around the code that does Naming.bind (), Naming.rebind (), and Naming.unbind () to skip registering and unregistering proxy objects. As shown below, all RMI calls in the server code should be wrapped in a simple if{} block using the m_bRunAsInProcessServer boolean flag. if(m_bRunAsInProcessServer == false) { Naming.rebind(proxyObj, proxyName); } Add a method to the Server class to extract direct references to proxy objects that implement remote interfaces. The client will use this method to access proxy objects, and it will cast these object references to the remote references they support. The client always goes through remote interfaces to access services on the server. It will never access any methods that are not part of remote interfaces. The following method allows the client-side code to directly access the server-side remote objects (proxies) if the client needs to access the API supported by those remote objects. Client code will use this method when it wants to run the server in its own process. public Object[] getServerSideProxies() { return m_oServerSideProxies; } Step 2: Modifications to the Client classThe client application has to be split into two classes. The sample code contains two files in the client package. They are:runClient.java — This class is responsible for obtaining the remote interfaces on the server and starting a client application.Client.java — This class implements all necessary client logic. It accepts the remote interfaces through its constructor and goes ahead to invoke services on the server.Step 3: Implement the runClient classWrite a few lines of startup code in the runClient class, which decides the method of connection with the server. We can switch between two modes of the server using a boolean flag. Let’s call this flag bUseRMI. If the flag is true, then the startup code tries to connect to the server through the Naming.lookup() method to obtain the remote interfaces on the server. When the flag is false, it creates a Server object and casts the server to obtain the remote interfaces. public static void main(String[] args) { if((args == null)||(args.length != 2)) { System.out.println("Usage: java com.company.product.client.runClient <usermi> <host_name>"); System.out.println("Usage: java com.company.product.client.runClient <normi> <working_directory>"); return; } boolean bUseRMI = false; String sHostName = null; String sWorkingDir = null; String str = args[0]; if(str.compareTo("use_rmi")==0) { bUseRMI = true; sHostName = args[1]; } else if(str.compareTo("no_rmi")==0) { bUseRMI = false; sWorkingDir = args[1]; } else { System.out.println("Usage: java com.company.product.client.runClient <use_rmi> <host_name>"); System.out.println("Usage: java com.company.product.client.runClient <no_rmi> <working_directory>"); return; } ServerInterface IServer = null; //Obtain Server Interface based on the boolean flag. if(bUseRMI) { IServer = getServerInterfacesThroughRMI(sHostName); } else { IServer = getServerInterfacesThroughInProcessServer(sWorkingDir); } if(IServer == null) { System.out.println("Failed to get Server Interface."); return; } //Start the client and pass in remote server interface. Client client = new Client(IServer); //Start client app. client.startClientApplication(); } As you can see, the following methods take care of isolating the RMI API from the Client class. These methods return a reference to ServerInterface: 1. getServerInterfacesThroughRMI(); 2. getServerInterfacesThroughInProcessServer(); getServerInterfacesThroughRMI(): This method does a Naming.lookup() to get a reference to a remote server object, then casts the remote object reference to the interfaces it supports. If the server is registering additional remote objects for each client, then this method logs on to the server to find the registry information about those remote objects. Do a few more Naming.lookup() calls to retrieve interface references on all those remote objects. The following code snippet shows how to obtain a remote interface through standard RMI API. private static ServerInterface getServerInterfacesThroughRMI(String sHostName) { //I assume that we know the name of the remote object. String sRemoteObjectName = "//" + sHostName + "/" + CoreC.SERVER_NAME; Object oServer = null;; try { //Connect to the remote object. System.out.println("starting lookup for " + sRemoteObjectName); oServer = Naming.lookup(sRemoteObjectName); System.out.println("Connected with " + sRemoteObjectName); } catch (Exception e) { System.out.println("runClient: an exception occurred: "); e.printStackTrace(); return null; } //Fetch server interface on this remote object. ServerInterface intf = (ServerInterface)oServer; return intf; } A note about getServerInterfacesThroughInProcessServer()This method internally creates an instance of the server and casts the server object to the remote interface it supports. (If the server is registering any additional remote objects, then this method creates those objects as well by calling the API defined in InProcessServerInterface. Additional interface references can be obtained by casting the remote objects to appropriate remote interfaces.) private static ServerInterface getServerInterfacesThroughInProcessServer(String sWorkingDir) { Server serverObj = null; try {serverObj = new Server(sWorkingDir,true);} catch (Exception e) { System.out.println("runClient.main: an exception occurred: " + e.getMessage()); e.printStackTrace(); return null; } ServerInterface intf = (ServerInterface)serverObj; return intf; } Step 4: Modifications to the Client classThe client object is constructed with the remote interface obtained in Step 3. The client does not care how we obtained the server interface. It just goes on to use the API defined in it. If the client needs some external input, that input should be part of the client’s constructor. As a whole, the objective is to keep the client unaware of the RMI-related API.package com.company.product.client; import com.company.product.core.*; public class Client { public Client(ServerInterface IServer) { m_IServer = IServer; } /** Do all the nice stuff here. */ public void startClientApplication() { if(m_IServer == null) { System.out.println("Client has a null server interface."); return; } /* * Invoke services on the server. */ try { m_IServer.doMyTaxes(null);} catch(Exception e1) { System.out.println("runClient: an exception occurred: "); e1.printStackTrace(); return; } return; } private ServerInterface m_IServer = null; }//public class Client With these steps in hand, we are all set to explore the benefits promised earlier. While debugging, use the boolean flag in the runClient class to switch between the two modes of the server. Now we can step into the server code directly from the client, when the client invokes methods on the remote interfaces.We used this concept successfully to make an RMI application run on a home PC. Other users of this design technique may need to customize the steps depending on the way their server and client classes are designed. Look at the included sample source code to gain a better understanding of what we did.Sample codeExtract the Javatip56.zip file (see Resources) into, for example, the C:temp directory. This will create a directory named javatip56 containing sample source code as well as various batch files. You do not have to modify any of the files if you are using Windows 95 or NT. Examine the batch files in the bin directory. Read the readme.txt file for details of all the scripts present in the bin directory. Try them and watch the results.NotesWhile you enjoy the benefits of this design technique, take a few precautionary measures to avoid any side effects in the development cycle: Comment out the in-process-server-related code (in the client) when the product goes to release.Whenever the remote interfaces on the server are modified, test the product through regular RMI setup.In the server implementation, be sure not to modify the input parameters passed into API defined on the remote interfaces. As Java treats objects through references, any changes to the input parameters will be visible in the client-side code when the client is using an in-process server. (When the client is using the RMI API to communicate with the server, these parameters are treated as values.) Traditional RMI methods treat return values and parameters as values.Continue to generate stubs and skeleton classes for remote objects.Performance-related tests should not be run through in-process server mode.ConclusionThis article tries to provide a workaround for the most common problems faced by Java developers using RMI. To ease debugging efforts, developers may have to customize the above described concept to suit their projects.Satheesh A. Donthy received his Masters degree from I.I.T. Kharagpur (India). He has been doing software development using object-oriented systems for the last six years. After working for four years at Intergraph doing CAD/CAM-related development, he switched to consulting. Since 1996, he has been consulting in and around the Boston area through Creative Software Technologies (www.creative-sw.com). He led the group that implemented the first spell checker written in 100 percent pure Java. Currently, he is working on implementing a client/server version of Spell Checker for Inso Corp. SecurityApp TestingJava