15.04.2010

OSGi und RMI

Man kann von EJB ja halten was man möchte, manchmal muss es aber sein.

Wenn man von einem Bundle aus auf eine EJB in einer anderen VM zugreifen will, kommt man aber nicht um einen RMI-Zugriff herum, z.B. (aus ADempiere):

private Object lookup(String jndiName) throws NamingException {
  InitialContext ctx = getInitialContext(Ini.isClient());
  return ctx.lookup(jndiName);
        }

Und was passiert?
javax.naming.CommunicationException 
         [Root exception is java.lang.ClassNotFoundException:         
         org.compiere.interfaces.StatusRemote 
         (no security manager: RMI class loader disabled)]

Irgendwie geht also unter OSGi der Security Manager verloren. Na gut - basteln wir eben einen (Vorsicht - der hier lässt dann gleich alles zu!):
private Object lookup(String jndiName) throws NamingException {
  System.setSecurityManager(new RMISecurityManager());
  InitialContext ctx = getInitialContext(Ini.isClient());
  return ctx.lookup(jndiName);
        }
Dann noch schnell ein Policy-File anlagen (Achtung! Auch gefährlich!)
grant {
         permission java.security.AllPermission "", "";
        };

Und starten mittels (nur als beispiel mit Equinox):
java -Djava.security.policy=client.policy 
             -jar org.eclipse.osgi_3.5.0.v20090520.jar -clean -console

Und was passiert?

java.lang.ClassCastException: 
       $Proxy0 cannot be cast to org.compiere.interfaces.Status

Hmpf. Naja klar, der RMI-Adapter verwendet den Context-Classloader des aufrufenden Threads, um die Instanzen vom Server zu initialisieren. Wenn das nicht gerade der ClassLoader des Bundles ist, in dem die Interfaces zu finden sind, klappt der Cast natürlich nicht.

Man muss also wirklich den zuständigen ClassLoader (oder das Bundle oder die Klasse) mit übergeben. Hier wars einfach: Die Interfaces liegen in dem Bundle, in dem auch die Methode selbst implementiert ist - es reicht also einfach:

private Object lookup(String jndiName) throws NamingException {
  System.setSecurityManager(new RMISecurityManager());
  InitialContext ctx = getInitialContext(Ini.isClient());
  ClassLoader prev = Thread.currentThread().getContextClassLoader();
  try {
   Thread.currentThread()
                              .setContextClassLoader(this.getClass().getClassLoader());
   return ctx.lookup(jndiName);
  } finally {
   Thread.currentThread().setContextClassLoader(prev);
  }
 }

Et voila!

Ich war nicht der erste mit diesem Problem, vielen Dank an: