I have 2 ejbs. Ejb-A that calls Ejb-B. They are not in the same Ear.
For portability Ejb-B may or may not exist on the same server. (There is an external property file that has the provider URLs of Ejb-B. I have no control over this.)
Example Code: in Ejb-A
EjbBDelegate delegateB = EjbBDelegateHelper.getRemoteDelegate(); // lookup from list of URLs from props...
BookOfMagic bom = delegateB.getSomethingInteresting();
Use Cases/Outcomes:
When Ejb-B DOES NOT EXIST on the same server as Ejb-A, everything works correctly. (it round-robbins through the URLs)
When Ejb-B DOES EXIST on the same server, and Ejb-A happens to call Ejb-B on the same server, everything works correctly.
When Ejb-B DOES EXIST on the same server, and Ejb-A calls Ejb-B on a different server, I get:
javax.ejb.EJBException: nested exception is: java.lang.ClassCastException: $Proxy126
java.lang.ClassCastException: $Proxy126
I'm using Weblogic 10.0, Java 5, EJB3
Basically, if Ejb-B Exists on the server, it must be called ONLY on that server.
Which leads me to believe that the class is getting loaded by a local classloader (on deployment?), then when called remotely, a different classloader is loading it. (causing the Exception) But it should work, as it should be Serialized into the destination classloader...
What am I doing wrong??
Also, when reproducing this locally, Ejb-A would favor the Ejb-B on the same server, so it was difficult to reproduce. But this wasn't the case on other machines.
NOTE: This all worked correctly for EJB2
So I was able to 'fix' this issue by adding a #RemoteHome(MyRemoteHome.class) to the bean
public interface MyRemoteMethods {
String myMethod() throws RemoteException; // this Ex is required
}
public interface MyRemote extends EJBObject, MyRemoteMethods {
}
public interface MyRemoteHome extends EJBHome {
public MyRemote create() throws CreateException, RemoteException;
}
then my bean
...
#RemoteHome(MyRemoteHome.class)
public class MyBean implements MyRemoteMethods {
...
Previously, only the bean and the Remote interface existed. (there is no local usage)
Overall, this fix makes my EJB 3 a bit more EJB 2-ish and not as clean (more code and classes involved). I am also unclear why I can do a jndi lookup for the RemoteHome interface (and call create) from my local and remote servers and not get a ClassCastException, but when I do this for the Remote interface, I do get a ClassCastException. Is this a bug in Weblogic? (I've also seen some posts similar using JBoss)
Related
I am trying to port 2 EJB modules in my application from EJB2.1 to EJB3.0. I am using the Eclipse Kepler IDE and regenerated the session beans using an EJB3.0 configuration. I am not using an ejb-jar.xml because in EJB 3.0 that is supposed to be redundant. I have instead used annotations for marking my bean as Stateless and specifying the Local and Local Home Interfaces. I have still kept the Local Home interface since I wanted the basic structure of my project to be similar to what it was in EJB2.1. I have also done away with the xml bindings for the EJB while migrating.
We are using a WAS 7 application server for deployment and while the EJB is getting successfully deployed without errors, I am getting a naming Exception while looking up my Local Home interface from a separate POJO class of a different web application it is required in. I basically want to call the create() method of the Local Home interface after referencing the EJB Local Home. Adding code samples below:
Session Bean:
#Stateless
#Local(AccessLDAPSessionLocal.class)
#LocalHome(AccessLDAPSessionLocalHome.class)
public class AccessLDAPSessionBean implements AccessLDAPSessionLocal {
//Business Logic
}
Local Interface:
public interface AccessLDAPSessionLocal {
//business Interface
}
Local Home Interface:
public interface AccessLDAPSessionLocalHome extends EJBLocalHome {
public AccessLDAPSessionLocal create() throws CreateException;
}
Pojo class referencing the Local Home interface:
public static AccessLDAPSessionLocal getAccessLDAPSessionBean() throws NamingException, CreateException {
if (accessLDAPSessionBean == null) {
InitialContext context = new InitialContext();
Object obj = context.lookup("java:global/AccessLDAP/AccessLDAPSessionBean!com.ibm.asset.hrportal.core.ejb.ldap.AccessLDAPSessionLocalHome");
accessLDAPSessionBean = ((AccessLDAPSessionLocalHome) obj).create();
}
return accessLDAPSessionBean;
}
Also my Local and Local Home interfaces are inside my EJB client which I use as a jar file, while my Session Bean is inside the actual EJB which is used as an EAR.
Following is the error I am getting:
NamingException::javax.naming.NameNotFoundException: Name global not found in context "java:".
Am I missing some configuration resulting in the failure of JNDI lookup? Any help would be gratefully appreciated. Thanks in advance.
WebSphere Application Server 7.0 is only an implementation of EJB 3.0, but the java:global namespace wasn't added until EJB 3.1, which wasn't implemented in WebSphere Application Server until 8.0. As with all EJB 3.0 implementations, you will need to lookup a vendor-specific binding name. You can find the WebSphere Application Server binding name by looking at the CNTR0167I messages in SystemOut.log. See the EJB 3.0 application bindings overview topic in the Knowledge Center if you would like to customize this binding name.
Regardless, it is not a best practice to directly lookup EJBs by their binding name. Instead, you should use an EJB reference. In EJB 3.0, that means using an annotation like this in an EE managed object (such as a servlet or another EJB):
#EJB
private AccessLDAPSessionLocalHome home;
In this case, the EJB container is required to find a target EJB within the same application that contains the EJB reference, so you do not need to explicitly configure a target binding name for the EJB reference.
If you need to access the EJB reference from a utility class rather than an EE managed class, then declare the EJB reference with a name on a managed class (such as a servlet or another EJB), and look it up from the utility class:
#EJB(name = "ejb/accessHome", beanInterface = AccessLDAPSessionLocalHome.class)
public class MyServlet { ... }
public class MyUtility {
...
InitialContext context = new InitialContext();
Object obj = context.lookup("java:comp/env/ejb/accessHome");
...
}
You can configure multiple such EJB references on the same managed EE class using the #EJBs annotation:
#EJBs({
#EJB(name = "ejb/accessHome", beanInterface = AccessLDAPSessionLocalHome.class),
#EJB(name = "ejb/other" beanInterface = Other.class)
})
public class MyServlet { ... }
If your EJB is packaged in a separate EAR, then note that this is not a portable configuration. See the "Local client views" section of the EJB modules topic in the Knowledge Center. Additionally, you will need to explicitly configure a binding name for the EJB reference.
I think the way you are looking up the ejb is not correct. The JNDI name would be something like "java:comp/env/". ejb-ref-name would be part of your web.xml
Also, you will need to give providerURL and factoryName to the context object before doing the lookup.
I am trying to build an application ear file with the following structure:
app.ear
--> lib
-- app-domain.jar
-- app-api.jar
-- app-common.jar
...
--> META-INF
-- application.xml
-- glassfish-application.xml
-- MANIFEST.MF
-- app-ejb.jar
-- app-rs.war
The app-api.jar file contains my remote interfaces like
#Remote
public interface LanguageService {
/**
* #return all languages known to the system
*/
List<Language> loadLanguages();
The implementation is contained in the app-ejb.jar file and looks like this:
#Stateless
#Remote(LanguageService.class)
#Path("/language")
public class LanguageServiceImpl extends ValidatingService implements LanguageService {
#PersistenceContext(unitName = "kcalculator")
EntityManager em;
#GET
#Produces("application/json")
#Override
public List<Language> loadLanguages() {
CriteriaQuery<Language> query = createLoadLanguageQuery();
return em.createQuery(query).getResultList();
}
And finally I want to provide this as an JAX-RS web service and thus have my implementation of the javax.rs.Application class in the app-rs.war file, which looks like this:
#ApplicationPath("/resources")
public class MyApplication extends Application {
#Override
public Set<Class<?>> getClasses() {
Set<Class<?>> s = new HashSet<Class<?>>();
s.add(LanguageServiceImpl.class);
return s;
}
This deploys without any problem, the application class is also detected. However, when i finally access the web service an internal server error occurs due to a NPE.
The LanguageServiceImpl cannot be looked up, the log contains the following entry:
Caused by: javax.naming.NameNotFoundException: No object bound to name java:module/LanguageServiceImpl!com.kcalculator.ejb.LanguageServiceImpl
at com.sun.enterprise.naming.impl.GlassfishNamingManagerImpl.lookup(GlassfishNamingManagerImpl.java:741)
at com.sun.enterprise.naming.impl.GlassfishNamingManagerImpl.lookup(GlassfishNamingManagerImpl.java:715)
at com.sun.enterprise.naming.impl.JavaURLContext.lookup(JavaURLContext.java:167)
at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:471)
... 63 more
Hence the file is considered a Pojo, and so the reference to the entity manager is not initialized, which finally results in the Nullpointer exception.
I am kinda stuck, as annotating the bean class and giving it a mapped name is not working. Putting my application class into the ejb.jar file does not solve the problem either.
Can anyone point out what i am missing here?
Additional comment:
What I found out in the meantime: If I add a stateless session bean to my app-rs.war file and register it in MyApplication, it works without any problem. There injecting the LanguageService works, too. So it seems the problem is related to the fact that the service implementing bean class is located in another artifact.
The problem could be that you have an EJB with a remote interface.
JAX-RS 1.1 states in 6.2 that JAX-RS annotations only need to be supported on no-interface beans and local interfaces:
JAX-RS annotations MAY be applied to a bean’s local interface or directly to a no-interface bean.
As indicated in one of the previous comments, a working solution was found by moving the session beans to the web archive as well. Thus the separation between the ejb .jar file and the disclosing web service containing project is gone, however it seems rational to have the services in the artifact that is also supposed to provide the web services.
Thanks for the hints, however it is still not clear to me (according to the specification) why the initially described approach should not be feasible (but i realized it isn't...).
This question already has answers here:
Eager / auto loading of EJB / load EJB on startup (on JBoss)
(2 answers)
Closed 6 years ago.
I'm looking for an entry point in an EJB deployed on JBoss.
Servlets have the load-on-startup tag to use in its web.xml.
I'm searching for similar init() functionality for an EJB.
That didn't exist for EJB until 3.1. With EJB 3.1 you can use a singleton bean to simulate that:
From Application Startup / Shutdown Callbacks:
#Startup
#Singleton
public class FooBean {
#PostConstruct
void atStartup() { ... }
#PreDestroy
void atShutdown() { ... }
}
Otherwise, you will need to rely on the good old trick to use a ServletContextInitializer.
There are some application-specific extension, e.g. lifecycle listener for Glassfish. Maybe there's such a thing for JBoss.
But if I were you I would try to rely on standard features as much as possible. The problem with non-standard extension is that you never know exactly what can be done or not, e.g. can you start transaction or not, etc.
This article describes seven different ways of invoking functionality at server startup. Not all will work with JBoss though.
Seven ways to get things started. Java EE Startup Classes with GlassFish and WebLogic
If you're targeting JBoss AS 5.1, and you don't mind using the JBoss EJB 3.0 Extensions, you can build a service bean to bootstrap your EJB. If your service implements an interface annotated with the #Management annotation and declares a method with the signature public void start() throws Exception, JBoss will call this method when it starts the service. You can then call a dedicated init() method on the EJB you want to initialize:
#Service
public class BeanLauncher implements BeanLauncherManagement
{
#EJB private SessionBeanLocal sessionBean;
#Override
public void start() throws Exception
{
sessionBean.init();
}
}
#Management
public interface BeanLauncherManagement
{
public void start() throws Exception;
}
More information on this, including additional life-cycle events, can be found here.
Managed Beans can be used to do some process at JBoss startup, you have to add entry of that managed bean in configuration file.
You should be able to add the following line to the top of the method you want to run at startup:
#Observer("org.jboss.seam.postInitialization")
I'm using 2 PU in stateless EJB and each of them is invoked on one method:
#PersistenceContext(unitName="PU")
private EntityManager em;
#PersistenceContext(unitName="PU2")
private EntityManager em2;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW )
public void getCandidates(final Integer eventId) throws ControllerException {
ElectionEvent electionEvent = em.find(ElectionEvent.class, eventId);
...
Person person = getPerson(candidate.getLogin());
...
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW )
private Person getPerson(String login) throws ControllerException {
Person person = em2.find(Person.class, login);
return person;
}
Those methods are annotated with REQUIRES_NEW transcaction to avoid this exception. When I was calling these method from javaFX applet, all worked as expected. Now I'm trying to call them from JAX-RS webservice (I don't see any logical difference as in both cases ejb was looked up in initial context) and I keep getting this exception. When I set up XADatasource in glassfish 2.1 connection pools, I got nullpointer exception on em2.
Any ideas what to try next?
Regards
Ok,
it's solved now. I'll share just in case somebody got tackled by similar thing.
Whole problem was with netbeans deploying. They overwrite the settings in glassfish connection pool and when you set them proper at runtime, you got npe's or missing password silly stuff. The place to edit this is sun-resources.xml. XML element has attributes datasource-classname and rs-type. What needs to be done in case of Derby database is:
<jdbc-connection-pool ...
datasource-classname="org.apache.derby.jdbc.ClientXADataSource"
res-type="javax.sql.XADataSource">
...
</jdbc-connection-pool>
Works like a charm now.
I'm using 2 PU in stateless EJB and each of them is invoked on one method
Indeed. But you're calling the second method from the first one so you're doing a distributed transaction and you need to use XA for this (at least for one of the resource since GlassFish supports the last agent optimization allowing to involve one non-XA resource). In other words, setting one of your datasource as a XADataSource is the way to go.
If you get an error when doing this, please add details about what you did exactly and the stacktrace.
When calling the second method from the first, it is not an EJB method call. It treats it as just a regular method call and does not look at #TransactionAttribute. If you want a call to the same EJB, you can inject the SessionContext and call getBusinessObject. Then call the method on the returned EJB.
I've added the following in my web.xml:
<ejb-ref>
<ejb-ref-name>ejb/userManagerBean</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>gha.ywk.name.entry.ejb.usermanager.UserManagerHome</home>
<remote>what should go here??</remote>
</ejb-ref>
The following java code is giving me NamingException:
public UserManager getUserManager () throws HUDException {
String ROLE_JNDI_NAME = "ejb/userManagerBean";
try {
Properties props = System.getProperties();
Context ctx = new InitialContext(props);
UserManagerHome userHome = (UserManagerHome) ctx.lookup(ROLE_JNDI_NAME);
UserManager userManager = userHome.create();
WASSSecurity user = userManager.getUserProfile("user101", null);
return userManager;
} catch (NamingException e) {
log.error("Error Occured while getting EJB UserManager" + e);
return null;
} catch (RemoteException ex) {
log.error("Error Occured while getting EJB UserManager" + ex);
return null;
} catch (CreateException ex) {
log.error("Error Occured while getting EJB UserManager" + ex);
return null;
}
}
The code is used inside the container. By that I mean that the .WAR is deployed on the server (Sun Application Server).
StackTrace (after jsight's suggestion):
>Exception occurred in target VM: com.sun.enterprise.naming.java.javaURLContext.<init>(Ljava/util/Hashtable;Lcom/sun/enterprise/naming/NamingManagerImpl;)V
java.lang.NoSuchMethodError: com.sun.enterprise.naming.java.javaURLContext.<init>(Ljava/util/Hashtable;Lcom/sun/enterprise/naming/NamingManagerImpl;)V
at com.sun.enterprise.naming.java.javaURLContextFactory.getObjectInstance(javaURLContextFactory.java:32)
at javax.naming.spi.NamingManager.getURLObject(NamingManager.java:584)
at javax.naming.spi.NamingManager.getURLContext(NamingManager.java:533)
at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:279)
at javax.naming.InitialContext.lookup(InitialContext.java:351)
at gov.hud.pih.eiv.web.EjbClient.EjbClient.getUserManager(EjbClient.java:34)
I think you want to access an EJB application (known as EJB module) from a web application in Sun Application Server, right ?
ok, let's go.
When you deploy an EJB into an application server, the application server gives it an address - known as global JNDI address - as a way you can access it (something like your address). It changes from an application server to another.
In JBoss Application Server, you can see global JNDI address (after starting it up) in the following address
http://127.0.0.1:8080/jmx-console/HtmlAdaptor
In Sun Application Server, if you want to see global JNDI address (after starting it up), do the following
Access the admin console in the following address
http://127.0.0.1:4848/asadmin
And click JNDI browsing
If your EJB IS NOT registered right there, there is something wrong
EJB comes in two flavors: EJB 2.1 and EJB 3.0. So what is the difference ?
Well, well, well...
Let's start with EJB 2.1
Create a Home interface
It defines methods for CREATING, destroying, and finding local or remote EJB objects. It acts as life cycle interfaces for the EJB objects. All home interfaces have to extend standard interface javax.ejb.EJBHome - if you a using a remote ejb object - or javax.ejb.EJBLocalHome - if you are using a local EJB object.
// a remote EJB object - extends javax.ejb.EJBHome
// a local EJB object - extends javax.ejb.EJBLocalHome
public interface MyBeanRemoteHome extends javax.ejb.EJBHome {
MyBeanRemote create() throws javax.ejb.CreateException, java.rmi.RemoteException;
}
Application Server will create Home objects as a way you can obtain an EJB object, nothing else.
Take care of the following
A session bean’s remote home interface MUST DEFINE ONE OR MORE create<METHOD> methods.
A stateless session bean MUST DEFINE exactly one <METHOD> method with no arguments.
...
throws clause MUST INCLUDE javax.ejb.CreateException
...
If your Home interface extends javax.ejb.EJBHome, throws clauses MUST INCLUDE the java.rmi.RemoteException. If it extends javax.ejb.EJBLocalHome, MUST NOT INCLUDE the java.rmi.RemoteException.
...
Each create method of a stateful session bean MUST BE NAMED create<METHOD>, and it
must match one of the Init methods or ejbCreate<METHOD> methods defined in the session
bean class. The matching ejbCreate<METHOD> method MUST HAVE THE SAME NUMBER AND TYPES OF ARGUMENTS. The create method for a stateless session bean MUST BE NAMED create but need not have a matching “ejbCreate” method.
Now create an business interface in order to define business logic in our EJB object
// a remote EJB object - extends javax.ejb.EJBObject
// a local EJB object - extends javax.ejb.EJBLocalObject
public interface MyBeanRemote extends javax.ejb.EJBObject {
void doSomething() throws java.rmi.RemoteException;
}
Now take care of the following
If you are using a remote EJB object, remote interface methods MUST NOT EXPOSE local interface types or local home interface types.
...
If your Home interface extends javax.ejb.EJBObject, throws clauses MUST INCLUDE the java.rmi.RemoteException. If it extends javax.ejb.EJBLocalObject, MUST NOT INCLUDE the java.rmi.RemoteException.
Now our EJB
public class MyBean implements javax.ejb.SessionBean {
// why create method ? Take a special look at EJB Home details (above)
public void create() {
System.out.println("create");
}
public void doSomething() throws java.rmi.RemoteException {
// some code
};
}
Now take care of the following
It MUST IMPLEMENTS javax.ejb.SessionBean. It defines four methods - not shown above: setSessionContext, ejbRemove, ejbPassivate, and ejbActivate.
Notice our bean DOES NOT IMPLEMENT our business interface because of EJB specification says:
For each method defined in the interface, there must be a matching method in the session bean’s class. The matching method must have:
The same name
The same number and types of arguments, and the same return type.
All the exceptions defined in the throws clause of the matching method of the session
bean class must be defined in the throws clause of the method of the local interface.
And YOU HAVE TO DECLARE a ejb-jar.xml file according to
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1">
<enterprise-beans>
<session>
<ejb-name>HelloWorldEJB</ejb-name>
<home>br.com.MyBeanRemoteHome</home>
<remote>br.com.MyBeanRemote</remote>
<local-home>br.com.MyBeanLocalHome</local-home>
<local>br.com.MyBeanLocal</local>
<ejb-class>br.com.MyBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
</ejb-jar>
If you do not have a local EJB object remove from the deployment descriptor above
<local-home>br.com.MyBeanLocalHome</local-home>
<local>br.com.MyBeanLocal</local>
If you do not have a remote EJB object remove from the deployment descriptor above
<home>br.com.MyBeanRemoteHome</home>
<remote>br.com.MyBeanRemote</remote>
And put in META-INF directory
Our jar file will contain the following
/META-INF/ejb-jar.xml
br.com.MyBean.class
br.com.MyBeanRemote.class
br.com.MyBeanRemoteHome.class
Now our EJB 3.0
// or #Local
// You can not put #Remote and #Local at the same time
#Remote
public interface MyBean {
void doSomething();
}
#Stateless
public class MyBeanStateless implements MyBean {
public void doSomething() {
}
}
Nothing else,
In JBoss put jar file in
<JBOSS_HOME>/server/default/deploy
In Sun Application Server access (after starting it up) admin console
http://127.0.0.1:4848/asadmin
And access EJB Modules in order to deploy your ejb-jar file
As you have some problems when deploying your application in NetBeans, i suggest the following
Create a simple Java library PROJECT (a simple jar without a main method)
Add /server/default/lib (contains jar files in order you retrieve your EJB's) jar files to your Java application whether you are using JBoss (I do not know which directory in Sun Application Server)
Implement code above
Now create another war PROJECT
Add our project created just above and add <JBOSS_HOME>/client (contains jar files in order to access our EJB's). Again i do not know which directory in Sun Application Server. Ckeck out its documentation.
See its global mapping address as shown in the top of the answer
And implement the following code in your Servlet or something else whether you are using JBoss
public static Context getInitialContext() throws javax.naming.NamingException {
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
p.put(Context.URL_PKG_PREFIXES, " org.jboss.naming:org.jnp.interfaces");
p.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");
return new javax.naming.InitialContext(p);
}
Or the following whether you are using Sun Application Server - put the file appserv-rt.jar (I do not know which past contain appserv-rt.jar in Sun Application Server) in your classpath
public static Context getInitialContext() throws javax.naming.NamingException {
return new javax.naming.InitialContext();
}
In order to access your EJB in our Servlet or something else
MyBeanRemote myBean = (MyBeanRemote) getInitialContext().lookup(<PUT_EJB_GLOBAL_ADDRESS_RIGHT_HERE>);
myBean.doSomething();
regards,
Last two answers are both correct in that they are things you need to change/fix. But the NoSuchMethodError you see is not from your code, nor from things trying to find your code (would produce some kind of NoClassDefFoundException, I think, were this the case). This looks more like incompatible versions of the JNDI provider provided by the container, and what the JNDI implementation in the Java library wants. That's a pretty vague answer, but, would imagine it is solvable by perhaps upgrading your application server, and, ensuring you aren't deploying possibly-stale copies of infrastructure classes related to JNDI with your app, that might interfere.
First, fix your web.xml and add the Remote Interface in it:
<ejb-ref>
<description>Sample EJB</description>
<ejb-ref-name>SampleBean</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>com.SampleHome</home>
<remote>com.Sample</remote> <!-- the remote interface goes here -->
</ejb-ref>
Then, regarding the java.lang.NoSuchMethodError, Sean is right, you have a mismatch between the version of the app server "client library" you are using inside NetBeans and the app server version (server-side). I can't tell you exactly which JARs you need to align though, refer to the Sun Application Server documentation.
PS: This is not a direct answer to the problem but I don't think you're currently passing any useful properties when creating your initial context with the results of the call to System.getProperties(), there is nothing helpful in these properties to define the environment of a context (e.g. the initial context factory). Refer to the InitialContext javadocs for more details.