Using RMI over IIOP

Remote Method Invocation (RMI) is a term you may have seen a number of times reading this book. You probably know that EJBs use RMI, but you may still wonder what exactly RMI is. Well, it is a Java-specific distributed object system that allows you to create and use remote objects. For example, an RMI object running on one server can get a reference to another RMI object running on another server. After it has that reference, it can invoke the methods of the remote object as if it were local.

As with CORBA applications, you write remote interfaces for an object and generate stubs and skeletons. Also like CORBA, RMI allows a client and a remote object to communicate through client stubs and server skeletons. The stub exposes the methods of the remote object, and the client makes requests against the stub. These requests forward to the server and pass through the server skeleton to the remote object.

The original form of RMI used a proprietary protocol, Java Remote Method Protocol (JRMP), to allow objects to communicate. This works very effectively in a Java-only environment; but as you have seen, many enterprises environment are heterogeneous. If your environment houses objects that are written in languages other than Java, you cannot use RMI with JRMP. One solution to this problem is to forget RMI and write all your objects as CORBA objects. However, this would lead to many problems. For example, writing CORBA interfaces in Java IDL is involved, and how would EJBs communicate over distributed systems? Another solution is to create RMI objects but allow them to use IIOP, the CORBA protocol, as a transport mechanism. This approach is known as RMI over IIOP, or RMI-IIOP.

As you will learn when you write the code example later in this section, you can use RMI-IIOP to allow RMI objects to communicate directly with CORBA objects and, likewise, CORBA objects with RMI objects. This approach to interacting with CORBA objects requires you to make a few changes to a typical RMI object's code, but you do not have to write any IDL. Another very important use of RMI-IIOP is providing a transport mechanism for EJBs. When you create stubs and skeletons for EJBs, RMI-IIOP is used as the default transport mechanism. You can also allow EJBs to communicate with CORBA objects by using the remote method invocation compiler (rmic) to generate IDL on your behalf. When you do this, you can write an EJB client using an alternative CORBA language binding, such as C++. You will learn how to use rmic in the next section of today's lesson.

RMI over JRMP Example

This first example shows you how to use RMI in a completely Java environment. The example allows a remote user to enter their name, and the local server prepends the name with hello and returns the string to the client. There are three Java classes that you must write to produce an RMI application:

  • An interface for the remote object

  • An implementation of the interface

  • A client for the remote object

Listing 19.5 shows the code for the interface for the remote object. As you can see, the interface is quite straightforward, but there are a couple of points to note. The first is that the interface must extend java.rmi.RemoteInterface. The second is that all methods that the interface declares must throw a RemoteException. This exception is the superclass of many of the exceptions that might occur during an RMI operation.

Listing 19.5. HelloUser.java
import java.rmi.RemoteException;
import java.rmi.Remote;

public interface HelloUser extends Remote {
    public String sayHello(String s) throws RemoteException;
}

You must now create the implementation of this interface. Naturally, this implementation class must implement the HelloUser interface, but is also must extend java.rmi.UnicastRemoteObject. This class provides an object with much of the basic functionality to allow an object to become remote. If you don't extend this class, you have to explicitly provide this functionality:

import java.rmi.server.UnicastRemoteObject;
import java.rmi.*;

public class HelloUserImpl extends UnicastRemoteObject implements HelloUser {

When you wrote the interface, you declared that all the methods throw RemoteExceptions; this is also true of the class constructer. As a result, you must provide a constructer that throws a RemoteException:

public HelloUserImpl() throws RemoteException {
}

After you write the constructer, you can implement the method bodies. In this example, there is only one method—sayHello(). Remember that you must declare that each method implementation might throw a RemoteException:

public String sayHello(String name) throws RemoteException {
    return "Hello "+name;
}

The final part of the implementation class is to create a main() method that creates an instance of the implementation class and registers it with an RMI registry. Registering an object with the RMI registry allows remote clients to look it up over a network. After a remote client finds the object, the registry returns a reference to the client. The client can use this reference to invoke the methods of the remote object. To register an object with the RMI registry, create an instance of the implementation class and then use the rebind() method of the Naming class to register the class instance:

public static void main(String args[]) {
    try {
        HelloUserImpl hui = new HelloUserImpl();
        Naming.rebind("HelloUser",hui);

The rebind() method's two arguments are a name for the object (the client can then look up this name) and a remote object. If an existing binding for the name exists, it is replaced with this new binding. Alternatively, you can bind a name by using the bind() method. However, if the name already exists, the method throws an AlreadyBoundException. To explicitly unbind a name associated with a remote object, you use the unbind() method. This method takes one parameter—the name to unbind. If the name is not already bound, the method throws a NotBoundException.

You have now written the implementation class. Listing 19.6 shows the full code for this class.

Listing 19.6. HelloUserImpl.java
import java.rmi.server.UnicastRemoteObject;
import java.rmi.*;

public class HelloUserImpl extends UnicastRemoteObject implements HelloUser {
    // Constructer throws RemoteException
    public HelloUserImpl() throws RemoteException {
    }

    // Implement method body
    public String sayHello(String name) throws RemoteException {
        return "Hello "+name;
    }

    public static void main(String args[]) {

        // Create and install a security manager
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }

        try {
            // create a HelloUser instance
            HelloUserImpl hui = new HelloUserImpl();

            //Register the account
            Naming.rebind("HelloUser",hui);
            System.out.println("Registered");
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
						

Now that you have written the remote object, you must write a client that accesses it. The client class is like any other Java class, except that it must create an instance of a remote object rather than one that is local. To do this, you again use the Naming class, but this time you invoke its lookup() method. This method accepts a single parameter—the name of the remote object—and returns a reference to the remote object. You specify the name as a URL using the rmi scheme. For example, to look up a remote object called example on a server named sams, you would pass the parameter rmi://sams/example. In the client you are building, the code appears as follows:

HelloUser hu = (HelloUser)Naming.lookup("rmi://localhost/HelloUser");

Notice that you have to perform an explicit cast on the object the lookup() method returns. You must perform the cast, because the lookup() method always returns an object of the type Remote. After you have a reference to the remote object, you can invoke its methods as if it where a local object. Listing 19.7 shows the complete code for this class, including the code that invokes the sayHello() method of the remote object.

Listing 19.7. HelloUserClient.java
import java.rmi.server.UnicastRemoteObject;
import java.rmi.Naming;

public class HelloUserClient {
    public static void main(String args[]) {
        if (args.length!=1) {
            System.err.println("Usage: java name");
        }
        try {
            // Lookup the remote object
            HelloUser hu = (HelloUser)Naming.lookup("rmi://localhost/HelloUser");

            // invoke the method
            System.out.println(hu.sayHello(args[0]));
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
						

You have now completed all the Java code this example requires. Now you must compile the Java classes you have just written and then create a skeleton and stub for the remote object. To create the skeleton and stub, you simply run the remote method invocation compiler (rmic) and pass it the name of the implementation class for which it should create the skeleton and stub:

rmic HelloUserImpl

After run, rmic creates two java class files—ClassName_Skel.class and ClassName_Stub.class. On the server, ensure that the J2EE server is running and then start the RMI registry:

  • start rmiregistry On Win32

  • rmiregistry & On Unix

Now you must run the HelloWorldImpl class so that the runtime invokes its main() method, which will register an instance of the class with the RMI registry. To run the class, you must pass two property name-value pairs to the Java Interpreter. The first property is java.rmi.server.codebase, which allows any remote process to dynamically load classes as required. If you don't set this property, you have to physically copy all of the classes to the client. The property value is a URL that points to the code base for the application. The URL can use either the HTTP scheme or the file scheme. For example,

java -Djava.rmi.server.codebase=http://sams.com/classes/

Note

The URL in this example does not resolve; it is purely for illustrative purposes.


In this application, either use the HTTP scheme and change the path so that it relates to your machine, or use the file scheme and again change the path to suit you machine:

java -Djava.rmi.server.codebase=file:/C:/classes/

The second property you must pass to the Java interpreter is the location of a security policy file. You must provide a security file different from the default policy file, because the default policy does not allow remote class resolution, which you require to run RMI applications with dynamic class loading. This lesson does not intend to explain policy files, refer to the appropriate document to learn more about security, but the following is an example of a security policy file that allows your application to execute:

grant {
    // Allow everything
    permission java.security.AllPermission;
};

Save this file as default.policy on both the client and the server in the directory containing the classes for this application. Finally, to complete running the HelloWorldImpl class, execute the following command (change forward and backward slashes to suit your system):

java -Djava.rmi.server.codebase=file:/C:/classes/ -Djava.security.policy=c:classes
default.policy HelloUserImpl <hostname>

The server-side is now complete. To use the remote object from a client, you must copy the client class (HelloUserClient) and the interface (HelloUser) to client machine. In common with the server, you must provide a more relaxed security policy to facilitate dynamic class loading. In this instance, use the same policy file that you used on the server. To run the client type, use the following command, substituting <username> for your name and <hostname> for the remote server's name:

java -Djava.rmi.server.codebase=http://<hostname>/classes/
-Djava.security.policy=default.policy HelloUserClient <username> <hostname>

When the code executes, the client gains a reference to the remote objects and dynamically loads the stub from the server. The client application is then able to invoke the remote object's methods through the stub.

RMI over IIOP Example

Previously, today's lesson told you that you can use RMI over IIOP to write CORBA objects, and that EJBs used IIOP as a standard transport protocol. After you write a remote object so that it uses IIOP rather than JRMP as a transport protocol, it can communicate with CORBA objects, and they can communicate with it. At a high-level, the process to achieve this is quite straightforward:

1.
Create an interface for the remote object.

2.
Write a Java implementation of the interface.

3.
Use rmic (with a special switch) to generate IDL stubs and skeletons.

4.
Use the CORBA naming service rather than the RMI registry.

As you may guess, creating an object for use over IIOP requires you to make some changes to a standard RMI application. To illustrate this, today's lesson will walk through the process of converting the previous example application so that it uses IIOP rather than JRMP.

The remote object interface requires no changes, so the code in Listing 19.5 is still applicable in this application. However, the implementation of this interface and the client application both require modification. The implementation class no longer extends UnicastRemoteObject, but extends javax.rmi.PortableRemoteObject instead.

public class HelloUserImpl extends PortableRemoteObject implements HelloUser {

The only other change to this class is that you no longer use Naming.rebind(). Instead of this, you must create an InitialContext object and then use its rebind() method. The InitialContext class implements Context, which represents a naming context that consists of pairs of name-object bindings. The InitialContext class has three constructors:

  • One creates a context based on a hashtable of environment information.

  • The second constructs a context without initializing it.

  • The third simply creates an initial context.

After you create the initial context, you call its rebind() method to bind the HelloUser instance (you created this previously) and a name for that instance:

Context ctx = new InitialContext();
ctx.rebind("HelloUser",hui);

That is the extent of the modifications to the implementation class. Listing 19.8 shows the complete, modified code for the class.

Listing 19.8. HelloUserImpl.java
import javax.naming.*;
import javax.rmi.PortableRemoteObject;
import java.rmi.*;

public class HelloUserImpl extends PortableRemoteObject implements HelloUser {
    public HelloUserImpl() throws RemoteException {
    }

    public String sayHello(String name) throws RemoteException {
        return "Hello "+name;
    }

    public static void main(String args[]) {

        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }

        try {
            HelloUserImpl hui = new HelloUserImpl();

            // Create an initial context
            Context ctx = new InitialContext();

            //Register the instance with initial context
            ctx.rebind("HelloUser",hui);
            System.out.println("Registered");
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
						

Like the implementation class, the client must also create an initial context rather than use Naming.lookup(). Previously, you used the following line of code to look up the remote object:

HelloUser hu = (HelloUser)Naming.lookup("rmi://localhost/HelloUser");

Now you replace this line with the following:

Context ctx = new InitialContext();
HelloUser hu = (HelloUser)PortableRemoteObject.narrow (ctx.lookup("HelloUser"),HelloUser
.class);

You can see that you do not simply cast the object returned by the lookup in this instance, but you use the narrow() method of the PortableRemoteObject class instead. This method accepts two parameters. The first is an object from which to narrow. The second is a java.lang.Class object to which to narrow. In this instance, the code references the Class object by appending .class to the name of the class.

That is the extent of the changes to the Java code. Listing 19.9 shows the complete, modified code for the client class.

Listing 19.9. HelloUserClient.java
import javax.naming.*;
import javax.rmi.PortableRemoteObject;

public class HelloUserClient {
    public static void main(String args[]) {
        if (args.length!=1) {
            System.err.println("Usage: java name");
        }

        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }

        try {
            // Create an initial context
            Context ctx = new InitialContext();

            // use narrow to narrow object to object of the given class type
            HelloUser hu = (HelloUser)PortableRemoteObject.narrow(ctx.lookup("HelloUser")
,HelloUser.class);

          System.out.println(hu.sayHello(args[0]));
        }
        catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
						

As you did previously, compile the entire Java source to generate the class files. After you have done this, you need to create a client stub and a server skeleton. Like the previous example, you use rmic, but in the current example, you pass rmic an iiop switch to create an IIOP skeleton and stub:

rmic -iiop HelloWorldImpl

When you execute this command, two new files are created—_HelloWorldImpl_Tie (the skeleton) and _HelloWorldImpl_Stub (the stub). You have now generated all the files you require. To run the application, you must first start the J2EE server. After you start this, you must start the Transient Name Server—previously, you started the rmiregistry. The Transient Name Server provides a CORBA naming service that also has JNDI access:

tnameserv

All that remains is for you to run HelloUserClient and HelloUserImpl. To do this, you must pass additional environment properties to the Java interpreter. The first is naming factory provider, which in this application provides naming functionality for the CORBA Naming Service. The second is naming provider; for more information on JNDI environment properties, see Day 4, “Introduction to EJBs.” Consequently, to start execute the implementation class on the server, issue the following command (remember to change the slash orientation to suit your system, and replace <hostname> with your server's name):

java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory -Djava.naming
.provider.url=iiop://<hostname>:900 -Djava.rmi.server.codebase=http://<hostname>/rmi/
 -Djava.security.policy=m:
midefault.policy HelloWorldImpl <hostname>

Note

This example uses port 900, but if you are using Unix (including Solaris and Linux), you must specify a port number greater than 1024 because the lower ports are privileged ports for reserved purposes.


To start the client, issue the following command substituting <username> for your name, and <hostname> for your hostname:

java -Djava.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory -Djava.naming
.provider.url=iiop://<hostname>:900 HelloWorldClient <username>

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.50.87