tags:

views:

31

answers:

3

I have an application FooApplication (code in foo.jar) with a plugin BarPlugin (code in foo-bar-plugin.jar). The application instantiates an instance of the plugin dynamically. It works great.

FooApplication has some functionality accessible via an RMI interface FooRemote. That also works great, except for one thing. FooRemote has a method to access Remote objects exported by plugins, and I get an java.rmi.UnmarshalException when I try to hand out one of those plugins to an RMI client.

public interface FooRemote extends Remote
{
    /* other methods */
    public RemoteControl getPluginRemoteControl(int i) throws RemoteException;
}
/** just a named Remote object for debugging purposes */
public interface RemoteControl extends Remote
{
    public String getName() throws RemoteException;
}

What I am doing in my FooRemoteImpl is the following:

/* just a test object */
private static class RC0 extends UnicastRemoteObject implements RemoteControl 
{
    public RC0() throws RemoteException { super(); }
    @Override public String getName() throws RemoteException { return "RC0"; }
}

@Override public RemoteControl getPluginRemoteControl(int i) 
       throws RemoteException 
{
    int j = i;
    if (j <= 0)
        return new RC0();

    Collection<RemoteControl> rclist = this.model.getApplicationPluginRemotes();
    for (RemoteControl rc : rclist)
    {
        if (--j == 0)
            return rc;
    }
    return null;
}

When I call FooRemote.getPluginRemoteControl(0), it hands out an instance of my dummy class RC0, and works fine from a client. When I call FooRemote.getPluginRemoteControl(1), it tries to hand out one of the real plugin remotes, and it fails:

??? Java exception occurred:
java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
    java.lang.ClassNotFoundException: com.example.plugin.BarPluginRemoteControl (no security manager: RMI class loader disabled)

    at sun.rmi.server.UnicastRef.invoke(Unknown Source)

    at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(Unknown Source)

    at java.rmi.server.RemoteObjectInvocationHandler.invoke(Unknown Source)

    at $Proxy5.getPluginRemoteControl(Unknown Source)

Caused by: java.lang.ClassNotFoundException: com.example.plugin.BarPluginRemoteControl (no security manager: RMI class loader disabled)

[ more stuff deleted ] 

What gives?

+2  A: 

You probably need to set up a SecurityManager. Your client cannot find the class com.example.plugin.BarPluginRemoteControl because there is no security manager in place to give it access.

Try the following:

To the client code add:

System.setSecurityManager(new java.rmi.RMISecurityManager());

Create a policy file called client.policy containing:

grant{
    permission java.security.AllPermission;
};

Pass the policy file into the client's startup command:

java -Djava.security.policy=client.policy  ... <main-class>...
dogbane
The RMI Registry also needs the classes on it's own classpath, in case you are running it in a separate VM.
darri
darri: that was the problem. (post as a separate answer and I'll accept)
Jason S
You should also have a security manager, though. For testing, use AllPermission and later tweak to as small a permission set as possible (unless you are on a well-secured LAN, and then I'd say stick with AllPermission).
mwhidden
+1  A: 

The RMI Registry also needs the classes on it's own classpath, in case you are running it in a separate VM. I remember wasting too much time learning this the hard way.

Glad I could help!

edit: apparently this is not the correct approach, see answer by the commenter.

darri
Please see my response. This does not fix the problem, but it is a workaround.
mwhidden
+2  A: 

Please, please, don't put the classes on the RMI Registry classpath. That works around the problem, but it is not the correct solution. Doing so means that each time you update your server code, you'll need to synch the classes with your RMI Registry and all your clients. The proper solution is to provide the needed classes on the server's codebase using an http or ftp URI, (not a file URI, please!). Then, the RMI Registry and your clients will be able to access the needed classes dynamically via http or ftp.

The only classes you should include on the codebase would be the Remote interfaces to your server, and any classes that appear as parameters to or return values from methods in those interfaces, as well as any exception classes thrown by those interfaces. I believe (but I'm not sure) that best-practices for this is to create a separate jar with a '-dl' suffix which includes only these class files.

mwhidden
Thank you for this, it has been duly noted!
darri
@mwhidden: +1 for being a good point. In my case this does not apply, however; I am running both the server (`java -jar myjar.jar`) and the client (MATLAB with classpath set to myjar.jar) off the same .jar file in the same location on the same machine, so by definition when the server updates the client does too.
Jason S
Hope I didn't jam that down anyone's throat. I just burned a lot of cycles while new to RMI when moving to a more distributed setup after having developed and tested locally like you are now. You still do not need the classpath set for RMI Registry. I'd recommend using at least a file: URI for your codebase (only works locally). Switch to ftp or http when going over the network. If you continue to use RMI I guarantee that you will get burned by having a classpath set for the registry.
mwhidden