Bug 7082 - Calling an RMI Server from a servlet produces stack trace
Summary: Calling an RMI Server from a servlet produces stack trace
Status: RESOLVED FIXED
Alias: None
Product: Tomcat 4
Classification: Unclassified
Component: Catalina (show other bugs)
Version: 4.0.4 Beta 1
Hardware: PC All
: P3 normal (vote)
Target Milestone: ---
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2002-03-13 11:53 UTC by Dave
Modified: 2005-03-20 17:06 UTC (History)
1 user (show)



Attachments
The test case to reproduce this bug (47.35 KB, application/octet-stream)
2002-03-27 13:31 UTC, Dave Oxley
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Dave 2002-03-13 11:53:09 UTC
Using jdk 1.4.0 and TC4.0.4b1 (haven't tried another jdk yet, but it works 
fine using TC3.3 and this jdk) and calling an RMI server produces the 
following stack trace:

java.rmi.ServerException: RemoteException occurred in server thread; nested 
exception is: 
	java.rmi.UnmarshalException: error unmarshalling arguments; nested 
exception is: 
	java.net.MalformedURLException: no protocol: Files/Apache
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:292)
	at sun.rmi.transport.Transport$1.run(Transport.java:148)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:144)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages
(TCPTransport.java:460)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run
(TCPTransport.java:701)
	at java.lang.Thread.run(Thread.java:536)
	at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer
(StreamRemoteCall.java:247)
	at sun.rmi.transport.StreamRemoteCall.executeCall
(StreamRemoteCall.java:223)
	at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:133)
	at com.staffplanner.utils.rmi.StaffplannerRemoteImpl_Stub.executeTask
(Unknown Source)
	at com.staffplanner.utils.rmi.RemoteController.execute
(RemoteController.java:76)
	at com.staffplanner.utils.rmi.RemoteServer.callRemote
(RemoteServer.java:56)
	at com.staffplanner.batchjobs.CrystalProcessServer.runCrystalReport
(CrystalProcessServer.java:15)
	at 
com.staffplanner.pages.tradingweeks.TradingWeekScrollPage.processCustomRequests
(TradingWeekScrollPage.java:316)
	at com.staffplanner.pages.base.ScrollPage.processFormSubmit
(ScrollPage.java:181)
	at com.staffplanner.pages.base.ScrollPage.processPost
(ScrollPage.java:129)
	at com.staffplanner.pages.base.StaffPlannerPage.doPost
(StaffPlannerPage.java:453)
	at com.staffplanner.pages.base.ScrollPage.doPost(ScrollPage.java:118)
	at com.staffplanner.servlets.StaffPlannerServlet.processRequest
(StaffPlannerServlet.java:227)
	at com.staffplanner.base.ServletBase.doPost(ServletBase.java:74)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:760)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter
(ApplicationFilterChain.java:247)
	at org.apache.catalina.core.ApplicationFilterChain.access$0
(ApplicationFilterChain.java:197)
	at org.apache.catalina.core.ApplicationFilterChain$1.run
(ApplicationFilterChain.java:176)
	at java.security.AccessController.doPrivileged(Native Method)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter
(ApplicationFilterChain.java:172)
	at org.apache.catalina.core.StandardWrapperValve.invoke
(StandardWrapperValve.java:243)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:566)
	at org.apache.catalina.core.StandardPipeline.invoke
(StandardPipeline.java:472)
	at org.apache.catalina.core.ContainerBase.invoke
(ContainerBase.java:943)
	at org.apache.catalina.core.StandardContextValve.invoke
(StandardContextValve.java:190)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:566)
	at org.apache.catalina.valves.CertificatesValve.invoke
(CertificatesValve.java:246)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:564)
	at org.apache.catalina.core.StandardPipeline.invoke
(StandardPipeline.java:472)
	at org.apache.catalina.core.ContainerBase.invoke
(ContainerBase.java:943)
	at org.apache.catalina.core.StandardContext.invoke
(StandardContext.java:2347)
	at org.apache.catalina.core.StandardHostValve.invoke
(StandardHostValve.java:180)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:566)
	at org.apache.catalina.valves.ErrorDispatcherValve.invoke
(ErrorDispatcherValve.java:170)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:564)
	at org.apache.catalina.valves.ErrorReportValve.invoke
(ErrorReportValve.java:170)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:564)
	at org.apache.catalina.valves.AccessLogValve.invoke
(AccessLogValve.java:468)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:564)
	at org.apache.catalina.core.StandardPipeline.invoke
(StandardPipeline.java:472)
	at org.apache.catalina.core.ContainerBase.invoke
(ContainerBase.java:943)
	at org.apache.catalina.core.StandardEngineValve.invoke
(StandardEngineValve.java:174)
	at org.apache.catalina.core.StandardPipeline.invokeNext
(StandardPipeline.java:566)
	at org.apache.catalina.core.StandardPipeline.invoke
(StandardPipeline.java:472)
	at org.apache.catalina.core.ContainerBase.invoke
(ContainerBase.java:943)
	at org.apache.ajp.tomcat4.Ajp13Processor.process
(Ajp13Processor.java:458)
	at org.apache.ajp.tomcat4.Ajp13Processor.run(Ajp13Processor.java:551)
	at java.lang.Thread.run(Thread.java:536)
Caused by: java.rmi.UnmarshalException: error unmarshalling arguments; nested 
exception is: 
	java.net.MalformedURLException: no protocol: Files/Apache
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:249)
	at sun.rmi.transport.Transport$1.run(Transport.java:148)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:144)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages
(TCPTransport.java:460)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run
(TCPTransport.java:701)
	... 1 more
Caused by: java.net.MalformedURLException: no protocol: Files/Apache
	at java.net.URL.<init>(URL.java:579)
	at java.net.URL.<init>(URL.java:476)
	at java.net.URL.<init>(URL.java:425)
	at sun.rmi.server.LoaderHandler.pathToURLs(LoaderHandler.java:743)
	at sun.rmi.server.LoaderHandler.loadClass(LoaderHandler.java:159)
	at java.rmi.server.RMIClassLoader$2.loadClass(RMIClassLoader.java:629)
	at java.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:257)
	at sun.rmi.server.MarshalInputStream.resolveClass
(MarshalInputStream.java:200)
	at java.io.ObjectInputStream.readNonProxyDesc
(ObjectInputStream.java:1503)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1425)
	at java.io.ObjectInputStream.readOrdinaryObject
(ObjectInputStream.java:1616)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1264)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:322)
	at sun.rmi.server.UnicastRef.unmarshalValue(UnicastRef.java:297)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:246)
	... 6 more
Comment 1 Remy Maucherat 2002-03-13 17:16:21 UTC
Well, the error strongly hints at the Sun RMI classes incorrectly dealing with 
spaces in the path. This should have an obvious workaround (install Tomcat in a 
path without spaces, as you would do on Unix). Fixing the bug could be complex. 
If you're really interested in having this fixed quickly, you should try to 
debug it further.
Comment 2 Dave Oxley 2002-03-13 17:39:13 UTC
I thought it was a bug in the JDK, but I tried it in TC3.3 with a space in the 
path and it worked.
Comment 3 Dave Oxley 2002-03-27 13:31:23 UTC
Created attachment 1434 [details]
The test case to reproduce this bug
Comment 4 Dave Oxley 2002-03-27 13:32:42 UTC
During the production of this test case another problem was found. If the 
class to be downloaded is in a jar file in the WEB-INF/lib directory then 
everything works, but if the class is in the WEB-INF/classes directory then 
the ClassNotFoundException is thrown.
Classes in lib should not be available to an RMI server, this is a security 
problem.
Comment 5 Dave Oxley 2002-03-27 13:44:25 UTC
Forgot to mention:

There is a problem with the JDK and spaces in the path, but if TC4 is 
installed in a directory without spaces then it still doesn't work.
The test case shows this problem.
Basically the difference between TC3.3 and TC4 is that the rmi server scans 
the Tomcat classpath for TC4 but scans the java.rmi.codebase path for TC3.3.
Comment 6 Dave Oxley 2002-08-19 09:47:12 UTC
Comments posted to the tomcat-user mailing list by Greg Trasuk 
[stratuscom@on.aibn.com]:
	I'm in the same boat trying to use RMI and/or Jini from Tomcat.  This 
isn't a complete answer to your question, as I'm still investigating the 
issue, but I'm posting what I know so far in the hope that it might help in 
your own solution, and also generate discussion that will guide my 
exploration. When all is said and done, if there's interest, I can post 
a "Catalina-RMI HOWTO" sort of document.

	Although I didn't try to run the test case that you attached to your 
bug report, I did take a look at it, and I think I know what's going on.  
Here's what I know so far (most of which you probably know already, but I'm 
summarizing for other folks on the list):

	When you pass an instance of some Serializable class as an argument to 
an RMI call (e.g. passing a command object, as in your test case), the RMI 
subsystem will serialize the object with an additional annotation indicating 
the locations from which the class's bytecode can be downloaded.  When you 
pass an exported object (e.g. a server object or an object that will receive 
callbacks from remote objects), the RMI subsystem creates and serializes a 
proxy object (otherwise known as the RMI stub object) in place of the actual 
object.  In either case, the remote RMI subsystem has to load the class that 
is called out in the serialized instance.  It does this by calling the 
RMIClassLoader.

	The RMIClassLoader object first tries to find the class locally (i.e. 
in the default classloader).  If it can't find it locally, it searches in the 
list of locations contained in the annotation mentioned above.  If the 
required class is available locally, no further headaches are caused, which 
may be why some people have had no problems using RMI under Tomcat - they 
probably had the serialized classes and/or proxy classes in the standard 
classpath/classloader setup.

	And there we find our problem.  (At this point you might want to have 
a look at the JSP snippet below) The annotation is determined by 
RMIClassLoader. According to the "RMI and Object Serialization FAQ" in the 
JDK1.31 API docs,

  "If the _Stub class was loaded by an RMIClassLoader, then RMI already knows 
which codebase to use for its annotation. If the _Stub class was loaded from 
the CLASSPATH, then there is no obvious codebase, and RMI consults the 
java.rmi.server.codebase system property to find the codebase. If the system 
property is not set, then the stub is marshalled with a null codebase, which 
means that it cannot be used unless the client has a matching copy of the 
_Stub classfile in the client's CLASSPATH. "

	If we're running a standalone application (and I believe also in 
Tomcat 3.x), we're using the system class loader, which has "no obvious 
codebase", so the java.rmi.server.codebase property gets used.  But what's the 
class loader used in Tomcat 4.x?  I looked at the source code for Tomcat 4.0.1 
(happens to be what I have on hand), and o.a.c.loader.WebAppClassLoader 
extends from o.a.c.loader.StandardClassLoader, which extends from 
java.net.URLClassLoader, which has a method called getURLs().  The
WebAppClassLoader.getURLs() method returns a list of all the repositories it 
will search when trying to load a class on behalf of the web app.  This list 
calls out all the jar's in WEB-INF/lib, common/lib, etc.

	Having not seen the source for RMIClassLoader, I suspect that the
getClassAnnotation(..) method checks to see if the classloader for the 
supplied class is a URLClassLoader, and if so, uses the results of the
getURLs() method call as "an obvious codebase".  This suspicion is supported 
by the last part of the JSP, where I create a classloader that extends from 
URLClassLoader but overrides getURLs() to return a phony url.  The phony url 
shows up as the class's annotation.

	So the exact error you quoted in the bug report shows something about 
a "protocol missing" MalformedURL exception, which is caused by the fact that 
the urls to the repositories contain spaces, since the RMI annotation is 
supposed to be a "space-separated list of URL's".  Thus the annotation
doesn't get parsed properly.   This may be a bug in Catalina's class loader
(i.e. should the returned urls have the spaces encoded to '%20'?) or possibly 
in the way RMIClassLoader uses the results of getURLs().  But it's not the 
problem.

	The problem is how to get our codebase into the annotation.  Clearly 
the java.rmi.server.codebase property is not used, since the class loader has 
a codebase.  But setting a system property doesn't feel right to me anyway, 
since in a webapp scenario, we're in a shared JVM, and we shouldn't be allowed 
to set system properties that will affect other webapps.  (Aside- we similarly 
can't follow the normal practise of setting our own RMISecurityManager, again 
since it doesn't play nice with the other webapps. When I tried it, it seemed 
to screw-up Tomcat's internals, as well.  We need to run Tomcat with security 
enabled, and set the appropriate permissions).

	Options as I see them:
	(1)-Use an object factory approach to create instances of classes.  If 
we get the factory object from the RMI server, it and all the instances it 
creates will have the server's codebase property already set, which skirts the 
whole issue.  It won't allow us to use locally-defined inner classes, however, 
so it's not great for callback objects (although I suspect we could have the 
created object call back to a local object)

	(2)-Load our local classes explicitly through a classloader that 
returns our codebase.  This is what I did in the last part of the test case.  
It seems like a pain in the butt, and also caused ClassCastExceptions, which 
is why the reference is to an Object.

	(3)-Replace the context's classloader with a class loader that adds a 
webapp-specific codebase to the repositories it lists.  I'm thinking of having 
a property defined in the application context to specify the codebase.  
There's already provision in server.xml to specify an alternate webapp 
classloader. Downside is that the application is then Tomcat-specific (not 
that anyone would want to use any other container...).

	I suspect that using RMI purely as a client (i.e. with no local objects
exported) and passing only instances of classes on the local classpath of both 
client and server (e.g. java.* classes) would work just fine, with no codebase 
issues at all, but I've yet to try it out.

	Ideas and comments, anyone?



Greg Trasuk, President
StratusCom Manufacturing Systems Inc. - We use information technology to solve 
business problems on your plant floor. http://stratuscom.ca


<test-case apology="I know it's ugly code">

<%@page import="ca.stratuscom.TestWebapp.SampleClass" %> <%@page 
import="java.rmi.server.RMIClassLoader" %> <%@page import="java.util.Date" %> 
<%@page import="java.rmi.RMISecurityManager" %> <%@page import="java.net.URL" %
> <%@page import="java.net.URLClassLoader" %>

<%!
private class MyLoader extends URLClassLoader {
  public URL[] getURLs() {
    URL retval=null;
    try { retval=new URL("http://bob/fred.jar"); }
    catch (Exception e) {}
    return new URL[] {
      retval
      };
  }

  MyLoader(URL urls[]) { super(urls); }
}
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html>
  <head>
    <title></title>
  </head>

  <body>
    <h1>Show Annotation for locally created class</h1>
	<%
    /* System.setSecurityManager(new RMISecurityManager()); */

    String codebase="http://localhost:8080/TestWebapp/TestWebapp.jar";
	Date dt=new Date();
    /*System.setProperty("java.rmi.server.codebase",codebase);*/
	String annotation=RMIClassLoader.getClassAnnotation(dt.getClass());
	SampleClass sample=new SampleClass();
	String sampleAnnotation=
              RMIClassLoader.getClassAnnotation(sample.getClass());
	%>
	Annotation for a Date instance is <%=annotation%>.
<br>
	Annotation for a SampleClass instance is <%=sampleAnnotation%>.
    <hr>
    <%
    Class sampCls=RMIClassLoader.loadClass
(codebase,"ca.stratuscom.TestWebapp.SampleCl
ass");
    SampleClass rmiSamp=(SampleClass) sampCls.newInstance();
    String localSampAnnotation=
              RMIClassLoader.getClassAnnotation(rmiSamp.getClass());
    %>
    <br>
    Annotation for localSamp loaded through RMIClassLoader is
    <%=localSampAnnotation%>
    <hr>

    <%
    URL codebaseURL=new URL(codebase);
    URL loaderURLs[]=new URL[] {codebaseURL};
    URLClassLoader urlLoader=new URLClassLoader(loaderURLs);

    Class urlCls=urlLoader.loadClass("ca.stratuscom.TestWebapp.SampleClass");
    Object urlSamp= urlCls.newInstance();
    String urlSampAnnotation=
              RMIClassLoader.getClassAnnotation(urlSamp.getClass());
    %>
    <br>
    Annotation for localSamp loaded through URLClassLoader is
    <%=urlSampAnnotation%>
    <hr>

    <%
    MyLoader myLoader=new MyLoader(loaderURLs);

    Class myCls=myLoader.loadClass("ca.stratuscom.TestWebapp.SampleClass");
    Object mySamp= myCls.newInstance();
    String mySampAnnotation=
              RMIClassLoader.getClassAnnotation(mySamp.getClass());
    %>
    <br>
    Annotation for localSamp loaded through MyLoader is
    <%=mySampAnnotation%>
    <hr>

    <address><a href="mailto:trasukg@THINKPAD"></a></address>
<!-- Created: Wed Jul 31 09:17:33 Eastern Daylight Time 2002 -->
<!-- hhmts start -->
Last modified: Fri Aug 09 01:01:14 Eastern Daylight Time 2002
<!-- hhmts end -->
  </body>
</html>

-- In SampleClass.java
package ca.stratuscom.TestWebapp;

public class SampleClass {}
</test-case>
Comment 7 Dave Oxley 2002-08-28 12:29:32 UTC
Bug in WebappClassLoader where external repositories is now fixed (4.1.10) and 
allows RMI with remote objects to work. This is actually better than 
setting 'java.rmi.server.codebase', because it will only affect the one 
webapp. The following code should be used to set up the remote repository:

// This stuff is for Tomcat 4.1.10 and above.
Method m = null;
try {
    ClassLoader cl = StaffPlannerServer.class.getClassLoader();
    Class clc = cl.getClass();
    if (clc.getName().equals("org.apache.catalina.loader.WebappClassLoader")) {
        Class[] classes = new Class[1];
        Object[] parms = new Object[1];
        classes[0] = String.class;
        parms[0] = codebase_url;

        m = clc.getMethod("addRepository", classes);
        m.invoke(cl, parms);
    }
}
catch (Exception e) {}

// And if we're running Tomcat 3.x or a different AppServer completely. Do it 
the old way.
if (m == null) {
    Properties p = System.getProperties();
    p.put("java.rmi.server.codebase", codebase_url);
    System.setProperties(p);
}