10 Replies Latest reply on Feb 23, 2010 8:51 AM by aslak

    Problems Getting Started

    andygibson

      So, after Dan mentioned Arquillian on a post regarding a Weld bug, I thought I'd take a look. I looked through Dan's example, the Demo in the source and the documentation. What I have, I think should work, but it isn't. Here's a run down of what I did.

       

      Grabbed the source from subversion, mvn install 'd it, and created a new maven application.I'm trying it with JBoss 6 M2 which I grabbed the other day. Looking at the demo, you just need to add a couple of dependencies, and create a class which has a @Deployer annotated method returning a JavaArchive :

       

      in src/main/java :

       

      @Stateless
      public class MessengerBean {
      
          public static final String TEXT = "This is my EJB message";
          
          public String getMessage() {
              return TEXT;
          }
      }
      

       

      in src/test/java :

       

      @RunWith(Arquillian.class)
      public class MessengerTest {
      
          @Deployment
          public static JavaArchive createDeployment() {
      
              return Archives.create("test.jar", JavaArchive.class).addClasses(
                      MessengerBean.class);
          }
      
          @EJB
          private MessengerBean messengerBean;
      
          @Test
          public void testMessage() {
              Assert.assertNotNull(messengerBean);
              Assert.assertEquals(MessengerBean.TEXT + "df", messengerBean
                      .getMessage()); //should fail
          }
      }
      

       

      I defined my POM as :

       

      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <groupId>arqtest</groupId>
          <artifactId>arquillianapp</artifactId>
          <version>0.0.1-SNAPSHOT</version>
          <name>arquillianapp</name>
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <configuration>
                          <source>1.6</source>
                          <target>1.6</target>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
      
          <profiles>
              <profile>
                  <id>jboss-remote-60</id>
                  <activation>
                      <activeByDefault>true</activeByDefault>
                  </activation>
                  <dependencies>
                      <dependency>
                          <groupId>org.jboss.arquillian</groupId>
                          <artifactId>arquillian-jboss-remote-60</artifactId>
                          <version>1.0.0-SNAPSHOT</version>
                      </dependency>
                  </dependencies>
              </profile>
          </profiles>
          <dependencies>
      
              <dependency>
                  <groupId>org.jboss.arquillian</groupId>
                  <artifactId>arquillian-junit</artifactId>
                  <version>1.0.0-SNAPSHOT</version>
                  <scope>test</scope>
              </dependency>
              <dependency>
                  <groupId>junit</groupId>
                  <artifactId>junit</artifactId>
                  <version>4.6</version>
                  <scope>test</scope>
              </dependency>
          </dependencies>
      </project>
      

      Initially, I had a lot of problems connecting to the container and then in the docs I see I need a jndi.properties with :

       

      java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
      java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
      java.naming.provider.url=jnp://localhost:1099

       

      So I add that, and now while it can now connect to the server I have other issues in that it can't inject the bean :

       

      -------------------------------------------------------------------------------
      Test set: org.arqdemo.MessengerTest
      -------------------------------------------------------------------------------
      Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.25 sec <<< FAILURE!
      testMessage(org.arqdemo.MessengerTest)  Time elapsed: 0.766 sec  <<< ERROR!
      java.lang.RuntimeException: Could not inject members
          at org.jboss.arquillian.testenricher.jboss.EJBInjectionEnricher.injectClass(EJBInjectionEnricher.java:69)
          at org.jboss.arquillian.testenricher.jboss.EJBInjectionEnricher.enrich(EJBInjectionEnricher.java:46)
          at org.jboss.arquillian.spi.util.TestEnrichers.enrich(TestEnrichers.java:43)
          at org.jboss.arquillian.impl.DeployableTestBuilder$InContainerContainer$1.invoke(DeployableTestBuilder.java:89)
          at org.jboss.arquillian.junit.Arquillian$4.evaluate(Arquillian.java:129)
          at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
          at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
          at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:44)
          at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
          at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
          at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
          at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
          at org.jboss.arquillian.junit.Arquillian$2.evaluate(Arquillian.java:101)
          at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
          at org.jboss.arquillian.junit.Arquillian$3.evaluate(Arquillian.java:115)
          at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
          at org.junit.runner.JUnitCore.run(JUnitCore.java:159)
          at org.junit.runner.JUnitCore.run(JUnitCore.java:138)
          at org.jboss.arquillian.junit.JUnitTestRunner.execute(JUnitTestRunner.java:60)
          at org.jboss.arquillian.protocol.servlet.ServletTestRunner.doGet(ServletTestRunner.java:83)
          at javax.servlet.http.HttpServlet.service(HttpServlet.java:734)
          at javax.servlet.http.HttpServlet.service(HttpServlet.java:847)
          at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:336)
          at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242)
          at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:276)
          at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
          at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:183)
          at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:95)
          at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126)
          at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70)
          at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
          at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
          at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
          at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
          at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:368)
          at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:872)
          at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:653)
          at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951)
          at java.lang.Thread.run(Thread.java:619)
      Caused by: javax.naming.NameNotFoundException: MessengerBeanBean not bound
          at org.jnp.server.NamingServer.getBinding(NamingServer.java:771)
          at org.jnp.server.NamingServer.getBinding(NamingServer.java:779)
          at org.jnp.server.NamingServer.getObject(NamingServer.java:785)
          at org.jnp.server.NamingServer.lookup(NamingServer.java:396)
          at org.jnp.server.NamingServer.lookup(NamingServer.java:399)
          at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:728)
          at org.jnp.interfaces.NamingContext.lookup(NamingContext.java:688)
          at javax.naming.InitialContext.lookup(InitialContext.java:392)
          at org.jboss.arquillian.testenricher.jboss.EJBInjectionEnricher.lookupEJB(EJBInjectionEnricher.java:89)
          at org.jboss.arquillian.testenricher.jboss.EJBInjectionEnricher.injectClass(EJBInjectionEnricher.java:63)
          ... 39 more

      For some reason it is looking for MessengerBeanBean.


      Overall though, it certainly does seem to fit the bill for making container unit testing as simple as plain old junit testing.

       

      Cheers,

       

      Andy

        • 1. Re: Problems Getting Started
          andygibson

          Oh wow my formatting sucked. Apologies. Here's most of the code without formatting :

           

          public class MessengerBean {

           

              public static final String TEXT = "This is my EJB message";
             
              public String getMessage() {
                  return TEXT;
              }
          }

           

           

           

          And

           

           

          @RunWith(Arquillian.class)
          public class MessengerTest {

           

              @Deployment
              public static JavaArchive createDeployment() {

           

                  return Archives.create("test.jar", JavaArchive.class).addClasses(
                          MessengerBean.class);
              }

           

              @EJB
              private MessengerBean messengerBean;

           

              @Test
              public void testMessage() {
                  Assert.assertNotNull(messengerBean);
                  Assert.assertEquals(MessengerBean.TEXT + "df", messengerBean
                          .getMessage()); //should fail
              }
          }

           

           

          The error I get is caused by :

           

          Caused by: javax.naming.NameNotFoundException: MessengerBeanBean not bound
              at org.jnp.server.NamingServer.getBinding(NamingServer.java:771)
              at org.jnp.server.NamingServer.getBinding(NamingServer.java:779)

           

           

          I just tried it with a Local interface on the EJB and I still get the same problem,

           

          Cheers,

           

          Andy

          • 2. Re: Problems Getting Started
            aslak

            Glad you gave it a try!

             

            The reason for your error is the bad/lack off proper EJB enricher support.

            http://anonsvn.jboss.org/repos/common/arquillian/trunk/testenricher-jboss/src/main/java/org/jboss/arquillian/testenricher/jboss/EJBInjectionEnricher.java

             

            Typical EJB 3.0 impl:

            {code:java}

            @Local(MyManager.class)

            @Stateless

            public class MyManagerBean implements MyManager {

             

            }

             

            public class MyManagerBeanTestCase {

             

              @EJB

              private MyManager manager;

            }

            {code}

             

            It currently assumes your beans JNDI name will be "injection point type name" + "Bean".

             

            So it's looking for: "java:/" + MyManager + "Bean"

             

             

             

            We need to go through "the rules of bean name creation" to make it more solid.

             

            Shout out if you want to contribute..

            • 3. Re: Problems Getting Started
              dan.j.allen
              What this boils down to is that we are not properly supporting no-interface EJBs. That's were the naming is getting messed up.
              • 4. Re: Problems Getting Started
                dan.j.allen

                What we should be doing is supporting the portable global JNDI name rules as defined in the Java EE 6 platform. Using "java:/" relies on a proprietary JBoss name.

                 

                From the EJB 3.1 specification:

                Each portable session bean global JNDI name has the following syntax:

                 

                java:global[/<app-name>]/<module-name>/<bean-name>[!<fully-qualified-interface-name>]

                 

                <app-name> only applies if the session bean is packaged within an .ear file. It defaults to the base name of the .ear file with no filename extension, unless specified by the application.xml deployment descriptor.

                 

                <module-name> is the name of the module in which the session bean is packaged. In a stand-alone ejb-jar file or .war file, the <module-name> defaults to the base name of the module with any filename extension removed. In an ear file, the <module-name> defaults to the pathname of the module with any filename extension removed, but with any directory names included. The default <module-name> can be overriden using the module-name element of ejb-jar.xml (for ejb-jar files) or web.xml (for .war files).

                 

                <bean-name> is the ejb-name of the enterprise bean. For enterprise beans defined via annotation, it defaults to the unqualified name of the session bean class, unless specified in the contents of the Stateless/Stateful/Singleton annotation name() attribute. For enterprise beans defined via ejb-jar.xml, it’s specified in the <ejb-name> deployment descriptor element.
                The container registers a separate JNDI name entry for each local business interface, each remote business interface, and any no-interface view, 2.x local home interface, and 2.x remote home interface. For the no-interface view, the last portion of the entry name is the fully-qualified bean class name.

                 

                In addition to the previous requirements, if the bean exposes only one of the applicable client interfaces (or alternatively has only a no-interface view), the container registers an entry for that view with the following syntax:

                 

                java:global[/<app-name>]/<module-name>/<bean-name>

                 

                The container is also required to make session bean JNDI names available through the java:app and java:module namespaces.


                The java:app prefix allows a component executing within a Java EE application to access an application-specific namespace. The resulting syntax is:

                 

                java:app/<module-name>/<bean-name>[!<fully-qualified-interface-name>]

                 

                Note that <module-name> is a required part of the syntax, even for names based on session bean components packaged within a stand-alone module.

                 

                The java:module prefix allows a component executing within a Java EE application to access a module-specific namespace. The resulting syntax is:

                 

                java:module/<bean-name>[!<fully-qualified-interface-name>]

                 

                Note that the existence of global JNDI names for the Local and no-interface client views does not imply that cross-application access to those entries is required. See Section 3.2.2 for more details.

                We may want to create an SPI for looking up EJBs. For Java EE 6 and beyond, we can provide a standard implementation. For containers which don't support portable global JNDI names, we would need a specific implementation.
                • 5. Re: Problems Getting Started
                  andygibson

                  Aslak - Thanks for the response, I modified my names and got it working.

                   

                  Dan - switching to the global jndi naming scheme would be ideal, especially since it is the standard for containers implementing 3.1 (including the web profile I believe).

                   

                  I've pulled down the source code for it and had a look through it as well as the notes from the IRC meetings, still trying to find my feet in there but it looks interesting,

                   

                  Cheers,

                   

                  Andy

                  • 6. Re: Problems Getting Started
                    aslak

                    For the Container lookup hook we have this:

                    https://jira.jboss.org/jira/browse/ARQ-63

                     

                    The problem with the EJB injection is that we can't really tell the Beans JNDI name based on the interface name used at the injection point, we need to know the implementation of that interface.

                     

                    I think our best bet for now is something like this:

                    {code:java}

                    Enumeration names = context.list("java:app/test/");

                    while(names.hasMoreElements())

                    {

                      NameClassPair pair = names.nextElement();

                      Object boundObject = context.lookup(pair.getName())

                      // look for implementation

                      // look at pair.getName for some fancy logic in case of multiple impls of same interface

                    }

                    {code}

                     

                    Spoke to Andrew some time ago and I got the impression they are looking into providing a hook into the EJB implementation so we can reuse their binding/lookup logic.

                    • 7. Re: Problems Getting Started
                      andygibson
                      Thinking aloud for a second, this is a fairly common problem, translating an @EJB injection  point into a bean. I mean, they do it internally in JBoss-AS, you are looking to do it here, and it is done in third party frameworks, for example, Wicket with the EJB extensions. One would have imagined that ideally, there would be some library that would provide the ability to take an injection point and get the EJB for it, possibly with some way of providing extensions for the different container types and their own custom jndi naming.
                      • 8. Re: Problems Getting Started
                        aslak

                        Reading through the Wicket Java EE extension, this is the StandardJndiNamingStrategy(pluggable strategy):

                        {code:java}

                            caldulateName(

                               field.getAnnotation(EJB.class).name(),

                               field.getType());

                         

                        public class StandardJndiNamingStrategy implements IJndiNamingStrategy
                        {
                            public String calculateName(String ejbName, Class ejbType)
                            {
                                return "java:comp/env/" + (ejbName == null ? ejbType.getName() : ejbName);
                            }
                        }
                        
                        {code}

                        • 9. Re: Problems Getting Started
                          dan.j.allen

                          Spoke to Andrew some time ago and I got the impression they are looking into providing a hook into the EJB implementation so we can reuse their binding/lookup logic.

                           

                          It would absolutely be useful to have this logic in a reusable library. Definitely look into turning this into a distributable JAR that we can pull into Arquillian.

                           

                          The JBoss AS integration module for Weld has the beginnings of an EJB lookup library...delegating to a resolver which is currently in the JBoss EJB 3 commons library. Perhaps a source for ideas.

                           

                          see JBossEjbServices

                          • 10. Re: Problems Getting Started
                            aslak

                            The JBoss AS integration module for Weld has the beginnings of an EJB lookup library...delegating to a resolver which is currently in the JBoss EJB 3 commons library. Perhaps a source for ideas.

                             

                            see JBossEjbServices

                            Looked at this before, it's a very JBoss specific hook, and a hook into the JBoss deployers during deployment..

                            (....maybe there is a way of getting the DeploymentUnit out for our deployment via jmx....?)

                             

                            I'm missing the EJB equivalent of CDI BeanManager.createInjectionTarget:

                            {code:java}

                               protected void injectNonContextualInstance(BeanManager manager, Object instance)    {       CreationalContext<Object> creationalContext =  manager.createCreationalContext(null);       InjectionTarget<Object> injectionTarget = (InjectionTarget<Object>) manager.createInjectionTarget(             manager.createAnnotatedType(instance.getClass()));       injectionTarget.inject(instance, creationalContext);    }

                            {code}

                            ... and woala.. any thing it can handle is injected in the provided instance..