RMI ClassCastException in OSGi client accessing EJ

2019-01-29 08:09发布

问题:

I have an OSGi application. Working with EJB context.lookup I had to set Thread context class loader as bundle class loader in order to be able to cast. Like this:

Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
Entity entity=bean.getOne();
System.out.println(entity.getClass().getClassLoader());

output is

org.apache.felix.framework.BundleWiringImpl@7468776f

This code works. The problem that I can't cast if I have ArrayList

Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
ArrayList<Entity> entities=bean.getMany();

This code returns ClassCastException.

Checking

ArrayList<Entity> temp=new ArrayList<Entity>();
System.out.println(temp.getClass().getClassLoader());

returns NULL - it means bootstrap class. How can it be fixed?

EDIT:

The most interesting, that ArrayList with String works, classic array works, but ArrayList and ArrayList with Entiry don't work.

Class Bean {
....
  @Override //THIS DOESN'T WORK
    public ArrayList<Entity> readMany() {
        Entity dir1=new Entity();
        dir1.setContent("1 test");
        Entity dir2=new Entity();
        dir2.setContent("2 test");
        ArrayList<Entity> result=new ArrayList<>();
        result.add(dir1);result.add(dir2);
        return result;
    }

    @Override //THIS WORKS
    public ArrayList<String> readMany2() {
        String str1=new String("1 test");
        String str2=new String("2 test");
        ArrayList<String> result=new ArrayList<>();
        result.add(str1);
        result.add(str2);
        return result; 
    }

@Override //THIS WORKS
public Entity[] readArray() {
    ArrayList<Entity> al=readMany();
    Entity[] ar=new Entity[al.size()];
    for (int i = 0; i < al.size(); i++) {
        ar[i]=al.get(i);
    }
    return ar;
}

@Override //THIS DOESN'T WORK
public ArrayList readSimpleArrayList() {
    ArrayList<Entity> gal=readMany();
    ArrayList al= new ArrayList();
    for (Entity obj : gal) {
        al.add(obj);
    }
    return al;
}
...
}

Here is the log

java.lang.ClassCastException: com.test.cmn.shd.base.dir.language.LanguageDirEntity cannot be cast to com.test.cmn.shd.base.dir.language.LanguageDirEntity at com.test.cmn.dt.base.Activator.start(Activator.java:83) at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:645) at org.apache.felix.framework.Felix.activateBundle(Felix.java:1977) at org.apache.felix.framework.Felix.startBundle(Felix.java:1895) at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:944) at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:931) at com.test.cmn.dt.loader.LoaderModel.startCoreModule(LoaderModel.java:149) at com.test.cmn.dt.loader.LoaderModel.access$100(LoaderModel.java:39) at com.test.cmn.dt.loader.LoaderModel$InstallAndStartModuleWorker.doInBackground(LoaderModel.java:79) at com.test.cmn.dt.loader.LoaderModel$InstallAndStartModuleWorker.doInBackground(LoaderModel.java:73) at javax.swing.SwingWorker$1.call(SwingWorker.java:296) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at javax.swing.SwingWorker.run(SwingWorker.java:335) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:744)

EDIT 2 - FULL CODE. This code is executed on client computer. JavaEE (GF4) is running on server. There are three osgi bundles:for server,for client and shared. The copy of shared is both on server and on clients and contains LanguageDirBeanRemote and LanguageDirEntity.

ClassLoader thatLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
try {
       Properties jndiProps = new Properties();
       jndiProps.put("java.naming.factory.initial", "com.sun.enterprise.naming.impl.SerialInitContextFactory");
       jndiProps.put("java.naming.factory.url.pkgs", "com.sun.enterprise.naming");
       jndiProps.put("java.naming.factory.state", "com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
       jndiProps.setProperty("org.omg.CORBA.ORBInitialHost", "x.x.x.x");
       jndiProps.setProperty("org.omg.CORBA.ORBInitialPort", "3700");
       InitialContext ctx = new InitialContext(jndiProps);
       LanguageDirBeanRemote bean=(LanguageDirBeanRemote)ctx.lookup("java:global/...");
       ArrayList<LanguageDirEntity> elements=bean.readDirectory();
      System.out.println("HERE I GET THE ERROR:"+elements.get(0).getContent());
} finally {
Thread.currentThread().setContextClassLoader(thatLoader);
}

EDIT 3 I opened a bug

回答1:

"X cannot be cast to X", in a classloader/OSGi context, generally means that you have two classloaders present in your system, both of which have loaded the class X, and that you're trying to pass an instance created using one classloader's copy of the class to code expecting the other classloader's copy.

Fixes are either to make sure that only one instance of that class exists in any classloader and is shared by all code (simplest), or to make sure that instances never cross the classloader context boundaries, or (more fragile) to make sure that everyone is finding EXACTLY the same implementation of that class (same exact bytecodes for the class and everything it inherits).



回答2:

As the other answer has said, the root of your problem is almost certainly that you have multiple copies of LanguageDirEntity loaded by different classloaders. The most likely cause of this is that you've inadvertently packaged the physical class into different bundles. So the first solution is to inspect your bundles for the class. If they're all in the same location, running

grep -r LanguageDirEntity *

is a quick and dirty way of finding them. (Are you using the Maven bundle plugin for your build? It's easy to inadvertently embed dependencies into bundles unless you get your poms right.)

The reason things work when you use a String in an ArrayList is that String will be provided by the system, and there will never be multiple copies of it loaded by different class loaders. The reason you have problems using ArrayList but don't have problems using the Entity classes directly is that the interaction of generics and collections introduces some extra casts which aren't needed in the direct-usage case.

Unpacking exactly what's going on is tricky without knowing what classes are packaged where. I'd guess, from the problems you're seeing, that Bean is not in the same bundle as the Activator. However, the basic idea is that every time the ArrayList is used by code (either to add elements in or read elements out), a cast will be done to cast the contents to 'Entity'. The more casts you do, in the more different bundles, the greater the chance of hitting classes which have been incompatibly loaded from different bundles. In this case, it looks like your Activator saw a different copy of the class than your Bean, so when the Activator attempts the (implicit, added by the compiler) cast, it's incompatible with the contents of the ArrayList.



回答3:

Thank you everybody. I've finally solved it. I'm writing how as I understand it. There are two ways to use gf-client with osgi client:

  1. Not official way - take glassfish bundles and manually install them via osgi api.
  2. Official way - copy glassfish/lib/gf-client.jar and glassfish/modules to client and in classpath add gf-clinet.jar.

I don't know how, I did by the first way. I can't call it wrong, because glassfish is osgi itself. When I did it by the second way this problem disappeared. So the problem was in different classloaders when you load from java classpath and from osgi bundle.