EJB 3.0. Provide interfaces to clients - java

Suppose I am writing enterprise session bean(v 3.0) for using it as remote bean.
I have written remote bussines interface, implemented interface's methods in a bean class(bean also uses another java objects to transfer data to client, that classes specified as serializable to be marshalled over the network) and now I want to provide that bean to clients. To use bean functionality client needs bussiness interface of that bean and also classes that are used to transfer data from bean to client.
To solve this problem I made a jar from that bean and provide it to clients. Clients then have to add this jar library to client's project and after that find the bean implementation using lookup method from Context class.
But when I make a jar it puts all bean classes to jar archive, so client can decompile jar's bytecode and to see the real implementation of my interface. It's not very good practice.
So how can I provide all the necessary interfaces to clients without providing the real implementation?

Put the client interface into a different package or project, and when you build the client jar, only include the class files for that client interface.

Related

Creating EJB remote references into a client witout EAR

Well, this is my development environment:
I am using Java 1.8 and Eclipse 4.5.1 (Mars), J2EE tools and plugins installed to work with WildFly 8 as Application Server
I created a Java EJB project, with a simple HelloWorld stateless session bean class. It implements 2 interfaces: #Remote and/or #Local, where I defined a String getHelloWorld() stub.
Now, I have my client for consuming the EJB: A Dynamic Web Project with one Servlet.
Inside the servlet, I can inject the ejb class using annotations, like this:
#EJB
private HelloWorldLocal bean;
or
#EJB
private HelloWorldRemote bean;
As you see, I declared the bean as HelloWorldLocal/HelloWorldRemote types. However, if I want to deploy and run my application,
I need to put the EJB and Web projects into an EAR first. That allows the Servlet to know and compile the HelloWorldLocal or HelloWorldRemote bean types, by simply adding the EJB project on
the Build Path as Project, or even by putting the EJB project as a Deployment Assembly directive.
I'd like to create a client outside the EAR (a remote Swing application or Remote WebSite). That means my client will
have not the chance to adding the EJB project interfaces as project references in the build path as I did with the EAR. Even if I want to call
the remote bean with JNDI, I need to cast the lookup() object to those types in order to use the bean methods.
The question is:
How can I get the HelloWorldRemote/HelloWorldLocal bean types from my remote client without EARs, if those interfaces are declared into an separate EJB Project?
(Of course, I dont want to create a .jar with the EJB project here).
And what about portable JNDI lookup? https://docs.oracle.com/cd/E19798-01/821-1841/girgn/index.html It should work if the EJB in the same JVM as your client.
I'm not sure I completely understand what you're asking, and I haven't done this in years, but the normal approach for this would to to create an EJB client JAR containing only the client's bean dependencies and remote interfaces. To best accomplish this, you'll want to use a tool such as Maven to build your project which contains an EJB plugin for this. If you're not using Maven, you're going to be limited by the tooling within Eclipse, though at one point it did contain EJB client generation support. The Wildfly documentation on EJB client setup might help also, but explicitly mentions that packaging the client is out of scope.
I'd like to share the only one solution that I found: I have to create a .jar file, with all the EJB interfaces (#Remote, #Local, and so on), and use it like referenced types on my remote clients.
This .jar would be outside any EAR. however, it needs jboss-client.jar because of the # annotations. So, I packaged all, interfaces and jboss-client.jar, exported them to a .jar and I added this new file in my EJB and clients projects as external jar! Now I am able to use the HelloWorld* types on EJB classes implementations and #EJB client variables.
If I want a remote Web client, I've placed the EJB project with a Web Project module, into a EAR - Definitely we need an EAR. The Web module has Servlets, and the servlets handles the #EJB variables and responses. So, Outside this EAR, I just need to use URL's to the servlets and, you got it: EJB method results are reached from remote web clients.
In desktop applications with JNDI, I just put the interfaces .jar like external jar into the client project. In that way, I can cast the .lookup() to HelloWorld* types and using its methods.
I hope it helps.

Correct way to lookup local EJB in websphere - Getting ClassCastException

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.
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.
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.
...
From the link provided by in bkail's answer, These are the steps that I followed to make it work.
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.
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.
In the caller EAR, get a reference to the local interface with either a lookup or injection.
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.

Can I look up an EJB via JNDI and invoke methods using reflection?

I'm using EJB 3.1. I need to get a references to one of the EJBs in a servlet and I'd rather not put an EJB interface jar on the class path to get the code to compile.
Is it possible to look an EJB via JNDI and find the method I want to invoke using reflection without ever strongly typing the object to an interface?
Yes, if you're looking up a local EJB interface, then you can look up and invoke a local EJB within the same application using reflection.
This should work if you're using a direct lookup or an EJB ref lookup because the Java EE spec requires the application server to make EJB module classes available to WARs within the same application. The EJB spec doesn't require support for local interfaces across applications, so if that's what you're doing, you'll have to check with your application server vendor.
This will not work in general for remote EJB interfaces because a client proxy needs to be created. If you're using RMI-IIOP (EJB 2.x remote or EJB 3 extending java.rmi.Remote), you might be able to cast the EJB lookup result to javax.rmi.CORBA.Stub and use the _servant_preinvoke or _invoke methods the same as a generated stub method would do.
(Ultimately, this is a lot of caveats just to avoid a compile-time dependency. It's probably not worth the fragility, so I would recommend finding a way to solve that and compile normally.)

Why to use JNDI? - Remote EJB invocations

Why only JNDI is used to access remote EJB (different JVM, differente host)? why not use the # EJB annotation?
In all EJB books mention that you can access remote EJB using #EJB annotation.
Example: http://docs.oracle.com/javaee/7/tutorial/doc/ejb-intro004.htm
32.4.4 Remote Clients
A remote client of an enterprise bean has the following traits.
It can run on a different machine and a different JVM from the enterprise bean it
accesses. (It is not required to run on a different JVM.)
It can be a web component, an application client, or another enterprise bean
It can be a web component, an application client, or another enterprise bean.
To a remote client, the location of the enterprise bean is transparent.
The enterprise bean must implement a business interface. That is, remote clients may not
access an enterprise bean through a no-interface view.
To create an enterprise bean that allows remote access, you must either:
Decorate the business interface of the enterprise bean with the #Remote annotation:
#Remote public interface InterfaceName { ... }
Or decorate the bean class with #Remote, specifying the business interface or
interfaces:
#Remote(InterfaceName.class)
public class BeanName implements InterfaceName { ... }
Client access to an enterprise bean that implements a remote business interface is
accomplished through either dependency injection or JNDI lookup.
To obtain a reference to the remote business interface of an enterprise bean through
dependency injection, use the javax.ejb.EJB annotation and specify the enterprise bean's
remote business interface name:
#EJB Example example;
To obtain a reference to a remote business interface of an enterprise bean through JNDI
lookup, use the javax.naming.InitialContext interface's lookup method:
ExampleRemote example = (ExampleRemote)
InitialContext.lookup("java:global/myApp/ExampleRemote");
The text above is incorrect? I have not seen any code that uses Dependency Injection (#EJB) to access a remote EJB. It is not possible?
Several post say it is not possible to use the # EJB annotation for calls to remote ejb:
https://stackoverflow.com/a/7842345/3167508
https://stackoverflow.com/a/16518704/3167508
PD: Excuse me. My English is basic.
I believe the main reason is that injection of remote (different JVM) EJB instances through
#EJB(lookup = "jndi_name")
is only supported by some Application Servers and only with specific configuration.
i.e. JBoss 7+ supports it only when you define the remote-outbound-connection in the standalone file (and add a jboss-ejb-jar.xml in the META-INF folder of deployed packages containing the reference to that connection).
Furthermore:
#EJB can only work in CDI managed beans
Using this approach forces you to define connection properties once per connection, while with the programmatic lookup you may vary them on every request (and you are free to handle contexts and connections manually, if needed).

How does client know the EJB bean implementation is remote or local in ejb3

I'm a newbie to EJB3. I want to know that how client know the EJB bean implementation is in remote or local. When i access the bean using InitialContext in client class i want to know wether that bean is local or remote? Sorry if I'm asking stupid question?
The type of interface is determined via annotations.
These can be put next to Interface class declaration:
#Local - declares a local business interface
#Remote - declares a remote business interface
Then when an EJB extends such interfaces, it uses the interface as a local/remote view. If it extends multiple interfaces, it has multiple views.
These can be put next to EJB class declaration:
#Local(com.example.LocalInterfaceClass) - declares a local business interface
#Remote(com.example.SomeRemoteInterfaceClass) - declares a remote business interface
#LocalBean - declares a no-interface view (the full bean definition used as an interface)
If multiple of above annotations are used in combination, the EJB has multiple interface views. If all are ommitted, the bean defaults to a no-interface view.
You can use JDK inbuilt annotation processing to process annotations during compile time (via javax.annotation.processing classes and javac commandline options). E.g. you could generate code or set options/switches.
You can use reflection to determine annotations at runtime.
Probably cleanest and simplest of all not to have dynamic lookup & behaviour, but just to commit to either Local or Remote for each client and hard-code the appropriate behaviour.
There are two different interfaces available when you are writing an EJB. One is remote and one is Local. Remote, as it name suggests is for remote client that want to remotely call (or fire) functions and get some results. On the other hand Local is designed to be used in a local environment, for example in a case if another EJB or even a POJO in your system is using that. The usage would be the same as when you want to use an EJB using its Remote interface. However, it has less headache for the server to handle that. That's the only reason you might want to use a Local interface instead of Remote interface. Local interface is not local to JVM but local like an other POJO class.
Local client view cannot be accessed:
When an EJB or web component is packaged in a different application's EAR packages.
When a web component is deployed in a web container, and EJBs are deployed in an EJB container, and these containers are separated (even if they are running on the same machine)
These are main factors in considering a Local or Remote interface:
Client: if your client is not a web component or another bean do not use Local
Beans & Beans: Are the beans loosely coupled? then it is a good idea to use Local
Scalability: Remote is always better for scalability
Local interfaces are recommended for entity beans, which helps with performance issue.
More to read:
http://onjava.com/pub/a/onjava/2004/11/03/localremote.html
http://www.conceptgo.com/gsejb/ov06.html
Using #Remote of your interface you can use as Remote interface
#Remote
public interface Cart {
}
Now, Implement this interface to EJB bean.
#Stateful
public class CartBean implements Cart {
}
Similarly using #Local annotation you can use as Local interface.

Categories

Resources