Rox
Rox

Reputation: 2917

@EJB annotation does not work for initialize bean within the EJB application

I have a Maven project with this structure:

-myproject
  -myproject-ear
  -myproject-service
    -webservice
  -myproject-ejb

In the myproject-ejb I have this java packages:

-src/main/java/
-src/test/java/

I have an EJB and the corresponding bean implementation in

-src/main/java/org/mypackage/MyBean.java
-src/main/java/org/mypackage/MyBeanImpl.java

In src/test/java/ I have a test called MyBeanTest.java with the following code:

import javax.ejb.EJB;
import org.mypackage.MyBean;
import org.junit.*;

public class MyBeanTest {

    @EJB
    private MyBean myBean;

    @Test
    public void testBean() {
        System.out.println("myBean: "+myBean); // prints null
        myBean.writeToDB("Hello", "World"); // fails since myBean is null
    }
}

When I run the unit test, the myBean is null. I am wondering why the @EJB annotation does not work. The test package is in the same application as the bean, so @EJB should work.

Any ideas?

EDIT 1
I found this link with the same problem as I have, but the solution there doesn´t seem to work for me. Am I doing anything wrong?

package org.myproject.ejb;

import java.util.Hashtable;
import java.util.Properties;

import javax.ejb.EJB;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;

import org.myproject.ejb.MyBean;
import org.jboss.ejb.client.ContextSelector;
import org.jboss.ejb.client.EJBClientConfiguration;
import org.jboss.ejb.client.EJBClientContext;
import org.jboss.ejb.client.PropertiesBasedEJBClientConfiguration;
import org.jboss.ejb.client.remoting.ConfigBasedEJBClientContextSelector;
import org.junit.*;

public class MyBeanTest {

    private MyBean myBean;

    @Before
    public void init() {
        try {
            Properties clientProp = new Properties();
            clientProp.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false");
            clientProp.put("remote.connections", "default");
            clientProp.put("remote.connection.default.port", "4447");
            clientProp.put("remote.connection.default.host", "localhost");
            clientProp.put("remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS", "false");


            EJBClientConfiguration cc = new PropertiesBasedEJBClientConfiguration(clientProp);
            ContextSelector<EJBClientContext> selector = new ConfigBasedEJBClientContextSelector(cc);
            EJBClientContext.setSelector(selector);

            Properties env = new Properties();
            env.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
            env.put(Context.SECURITY_PRINCIPAL, "admin");
            env.put(Context.SECURITY_CREDENTIALS, "testing");
            InitialContext ctx = new InitialContext(env);
            myBean = (MyBean) ctx.lookup("java:app/myproject-ejb-1.0-SNAPSHOT/MyBeanImpl");
        } 
        catch(NamingException ex) {
            ex.printStackTrace();
        }
    }

    @Test
    public void testBean() {
        System.out.println("ejb: "+myBean); // prints null
    }
}

The error I get with the above configuration is:

WARN: Unsupported message received with header 0xffffffff
javax.naming.NoInitialContextException: Need to specify class name in environment or system property, or as an applet parameter, or in an application resource file:  java.naming.factory.initial
    at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:662)
    at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:307)
    at javax.naming.InitialContext.getURLOrDefaultInitCtx(InitialContext.java:344)

Upvotes: 0

Views: 10302

Answers (4)

Minh Kieu
Minh Kieu

Reputation: 475

This is my workaround but I do not like it:

In your test class, use @Inject to retrieve the service. Pass it around where needed. Or @Mocked the InitalContext, in the "new Expectations(){{}}" hookup the lookup and return the instance. If you have @EJB inside a service then you can use Deencapsulation.setField() to inject the dependencies.

Upvotes: 0

Glen Best
Glen Best

Reputation: 23105

  1. Container resource injection, such as @EJB, requires a populated JNDI directory and only works within Java EE managed components executing in a Java EE container. Is a challenge for unit testing. See JSR318 Java EE 6 Platform Spec, section EE.5 Resources, Naming, and Injection.

  2. You're now attempting JNDI lookup - Java SE unit test app remotely connecting its JNDI Context. Disadvantages: must deploy full Java EE 6 app as precondition to run test; test-bugfix-build-deploy-retest lifecycle can slow things.

    Some issues:

    • Your username/password properties are different than JBoss doc;
    • From doc it appears JNDI lookup name needs to be "ejb:..." rather than "java:app/..." because the JBoss EJB-client-project code uses this to intercept the lookup. Also from Java EE 6 platform spec EE.5.2.2: Names in java:app namespace are shared by all components in all modules in a single Java EE app. If your test is a separate JSE app using java:app, I suspect JBoss treats it as separate to the single Java EE application, and lookup will fail.
    • Make sure you lookup the interface, not the implementation class (i.e. the EJB no interface view) for remote access
    • You're refering to an unusual reference showing direct use of EJBClientConfiguration & EJBClientContext. It seems this is not required/preferred.

    Try these actions:

  3. In future: use CDI for injection; JUnit + CDI @Mock for "POJO" unit testing; Arquillian for "Java EE" unit/module testing in containers. Then you could avoid/reduce tests like (2) above (JSE client -> EJB). CDI supports:

    • Java EE resource injection into POJOs (including @EJB annotation). This still requires a deployed Java EE app/component and populated JNDI directory to lookup.
    • Managed beans as POJOs or Java EE components (incl. EJBs) - inject "any" to "any" with superior @Inject annotation. Works without JNDI directory, is typesafe & bean scope-aware.

    • Supports unit testing via simple mocking. Use @Mock & @Specializes to declare replacement version for any bean. Test EJB clients without EJBs. Test EJBs as POJOs.

    To enable CDI, include a beans.xml file (can be empty, if all config via annotation).

    To declare a managed bean:

    • optional scope above class e.g. @SessionScoped
    • no-arg constructor / @Inject on constructor

    Use this to inject a reference:

     @Inject (optional @MyDeclaredQualifier) private MyBean myBean;
    

    Arquillian ("JUnit for Java EE 6") runs test code itself on a Java EE server. It dynamically deploys test code to configured container(s) and runs tests. It supports @EJB annotation, JNDI connection becomes simple and you can include Java EE classes in unit tests without mocking, or refactoring to abstract away from them.

Upvotes: 5

Aspect
Aspect

Reputation: 152

This exception is thrown when no initial context implementation can be created. The policy of how an initial context implementation is selected is described in the documentation of the InitialContext class.

This exception can be thrown during any interaction with the InitialContext, not only when the InitialContext is constructed. For example, the implementation of the initial context might lazily retrieve the context only when actual methods are invoked on it. The application should not have any dependency on when the existence of an initial context is determined.

Upvotes: 1

Ravi Trivedi
Ravi Trivedi

Reputation: 2360

1) Annotation injection is done by container. So the class which is not managed(container managed) will not be able to do annotation injection.

2) Now, in this scenarios, you will have to make a manual call to JNDI and retrieve EJB instance:

ie:

InitialContext ctx = new InitialContext();      
MyBean bean = (MyBeanRemote) ctx.lookup("java:global/<portable jndi name of your bean>");

Note: The use of no arg constructor InitialContext(). Because your java class is deployed in a server I presume. Or else you may need to specify context factory class if your class is a standalone java class, depending on the vendor.

Note: You will need Bean Remote interface if you are making a call to EJB from a different application (ie: different war, ear ...) or else Local interface is enough.

Upvotes: 2

Related Questions