0

I have an EJB which is exposed by both local and remote interfaces

package com.sam.enqueue;

import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.Singleton;

@Singleton
@Local(SamEnqueueLocal.class)
@Remote(SamEnqueueRemote.class)
public class SamEnqueue implements SamEnqueueRemote, SamEnqueueLocal {


}


// remote interface
package com.sam.enqueue;

import javax.ejb.Remote;

@Remote
public interface SamEnqueueRemote {


}

// local interface
package com.sam.enqueue;

@Local
public interface SamEnqueueLocal {



}

My app container is websphere 8.0 and I am not overriding the default JNDI names which the server assigns. During server startup I get the following default bindings in logs:

CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application.  The binding location is: ejb/SAM_ENQUEUE/SAM_ENQUEUE.jar/SamEnqueue#com.sam.enqueue.SamEnqueueRemote
CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application.  The binding location is: com.sam.enqueue.SamEnqueueRemote
CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueRemote interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application.  The binding location is: java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueRemote
CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application.  The binding location is: ejblocal:SAM_ENQUEUE/SAM_ENQUEUE.jar/SamEnqueue#com.sam.enqueue.SamEnqueueLocal
CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application.  The binding location is: ejblocal:com.sam.enqueue.SamEnqueueLocal
CNTR0167I: The server is binding the com.sam.enqueue.SamEnqueueLocal interface of the SamEnqueue enterprise bean in the SAM_ENQUEUE.jar module of the SAM_ENQUEUE application.  The binding location is: java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueLocal

The lookup class is just a simple java class in a different EAR in the same server with the following code:

Context ctx = new InitialContext();
Object local = ctx.lookup("java:global/SAM_ENQUEUE/SamEnqueue!com.sam.enqueue.SamEnqueueLocal");

SamEnqueueLocal samEnqueue = (SamEnqueueLocal) local;

The lookup is working with any of the three JNDI names for the local but it's not getting cast to SamEnqueueLocal. The exception trace is:

SystemErr     R java.lang.ClassCastException: com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f incompatible with com.sam.enqueue.SamEnqueueLocal

... rest ommited

I've made a shared library and put the stub of destination EAR in it. The library is the classpath of the source lookup EAR with the Classes loaded with local class loader first (parent last) policy. The library is not isolated. If I remove the stub, I get a java.lang.ClassNotFoundException: com.sam.enqueue.SamEnqueueLocal as expected.

Update:

While using Dependency Injection:

@EJB(lookup="ejblocal:com.sam.enqueue.SamEnqueueLocal")
private SamEnqueueLocal samEnqueueLocal;

The error I get is:

javax.ejb.EJBException: Injection failure; nested exception is: java.lang.IllegalArgumentException: Can not set com.sam.enqueue.SamEnqueueLocal field com.some.SomeBean.samEnqueueLocal to com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f
Caused by: java.lang.IllegalArgumentException: Can not set com.sam.enqueue.SamEnqueueLocal field com.some.SomeBean.samEnqueueLocal to com.sam.enqueue.EJSLocal0SGSamEnqueue_cf56ba6f

So it's basically the same.

ares
  • 4,283
  • 6
  • 32
  • 63

3 Answers3

1

You are getting java.lang.ClassCastException because you are retrieving a reference to an EJB that exists in a different class loader than the deployment unit (ejb-jar, war, etc) that is trying to inject it.

Using local EJB references between applications is vendor dependent if possible at all. You may be able to deploy your SamEnqueue bean in a separate EJB module and try to reference it via a manifest Class-Path: entry from each application. Be sure that there are no copies of SamEnqueueLocal in either EAR file.

Alternatively, just use the SamEnqueueRemote interface.

Refer to chapter 8 of the Java EE specification for more information.

Steve C
  • 18,876
  • 5
  • 34
  • 37
  • I got this to work after I removed the stub from lib and then set [Class loader policy](http://www-01.ibm.com/support/knowledgecenter/SSAW57_8.5.5/com.ibm.websphere.nd.doc/ae/crun_classload.html?cp=SSAW57_8.5.5%2F1-8-2-5-0) from *Multiple* to *Single*. BTW, what is the syntax for manifest entry to refer to another ear? – ares Sep 16 '15 at 12:05
  • Turns out, I have to use isolated class loader because there are other ears on the same server (legacy code) with different versions of same class in different ears. So the second option of trying to refer it by specifying `Class-Path:` in manifest file. I tried some things but they didn't work out. Can you show an example? – ares Sep 16 '15 at 14:31
1

See the "Local client views" section of the EJB modules topic in the knowledge center:

The EJB specification only requires local client views to be supported for EJBs packaged within the same application. This includes local homes, local business interfaces, and the no-interface view. WebSphere® Application Server permits access to local client views to EJBs packaged within a separate application with some restrictions

  • The local interface and all parameter, return, and exception types used by the local interface must be visible to the class loader of both the calling application and the target EJB application. You can ensure this by either using a shared library associated with a server class loader or by using an isolated shared library associated with both applications. Read the Creating shared libraries topic for more information.

...

Brett Kail
  • 33,593
  • 2
  • 85
  • 90
  • Okay.. So I uninstalled the EAR containing target session beans and its interfaces. I created an isolated shared library, put the sub of my the jar in that lib and added it to the classpath of source EAR. The trouble is that the Session beans in the lib are not initialized and I'm getting `javax.naming.NameNotFoundException: Name "com.sam.enqueue.SamEnqueueLocal" not found in context "ejblocal:".` – ares Sep 18 '15 at 08:51
1

From the link provided by in bkail's answer, These are the steps that I followed to make it work.

  1. Take out the Local and Remote interfaces i.e. SamEnqueueRemote and SamEnqueueLocal from my source EJB jar and package then into a separate jar file. Although just taking out the Local interface will also work.
  2. Make a shared library and put this jar into it. The shared library must be isolated so that same version of class is loaded by caller and callee.
  3. In the caller EAR, get a reference to the local interface with either a lookup or injection.
  4. Deploy both the caller and callee to the server and make sure to add the shared library in the classpath of both the EARs.

One of the approaches mentioned in this link is similar.

One way to prevent this is to use a remote interface. As Paul mentioned, there is optimization that happens when the caller and callee are in the same JVM, so it's not as expensive as if they're in separate JVMs. The ORB has classloader machinery that ensures the classes for the caller and callee are loaded using classloaders compatible with each side of the call.

Option 2, including the ejb jar within the new ear, won't solve the problem. Even though the classes will be available in both classloaders, the object passed by-reference from callee back to caller will still be instanciated using the other application's classloader and will not be type-assignable. Same with Option 3.

The 2nd way to make it work is to place the classes used by caller and callee in a "WAS shared library" and configure both applications to use that shared library. The subject of shared libraries and how to configure them is described in the WAS InfoCenter documentation...search on "shared library."

The 3rd way to make it work, which is the least desirable of the three, is to change the WAS server classloader policy to "one classloader per server." The default, as I mentioned at the top, is "one classloader per application (EAR file)." Changing to one classloader per server ensures that everything gets loaded by the same classloader and is thus type-compatible, but deprives your applications of the isolation/security benefits that come from having each app in its own classloader.

Community
  • 1
  • 1
ares
  • 4,283
  • 6
  • 32
  • 63