glassfish
  1. glassfish
  2. GLASSFISH-21042

Interfaces Specified by RemoteHome or LocalHome Not Resolved in SerialContext.lookup as Business Interfaces on EJB Session Beans, Resulting in NPE in JavaGlobalJndiNamingObjectProxy.create

    Details

    • Type: Bug Bug
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: 3.1.1
    • Fix Version/s: None
    • Component/s: ejb_container
    • Labels:
      None

      Description

      Had the following error in a JUnit test case using the embedded Glassfish 3.1.1 container:

      shouldCreateABook(com.coverity.testsuite.ejb.localhome.BookLocalHomeBeanTest):
      Communication exception for SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory,
      java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl,
      java.naming.factory.url.pkgs=com.sun.enterprise.naming}
      
      BookLocalHomeBeanTest.java
      package com.coverity.testsuite.ejb.localhome;
      
      import java.io.File;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;
      
      import javax.ejb.embeddable.EJBContainer;
      import javax.naming.Context;
      import javax.naming.InitialContext;
      import javax.xml.ws.WebServiceRef;
      
      import org.junit.After;
      import org.junit.AfterClass;
      import org.junit.Before;
      import org.junit.BeforeClass;
      import org.junit.Test;
      
      import com.coverity.testsuite.ejb.Book;
      import com.coverity.testsuite.ejb.localhome.BookLocal;
      
      import static org.junit.Assert.*;
      
      /**
       *
       * @author pasc
       */
      public class BookLocalHomeBeanTest {
      
          private static EJBContainer ec=null;
          private static Context ctx=null;
      
          public BookLocalHomeBeanTest() {}
      
          @BeforeClass
          public static void initContainer() throws Exception {
              Map<String, Object> props=new HashMap<String, Object>();
              props.put(EJBContainer.MODULES, new File("target/embedded-classes"));
              props.put("org.glassfish.ejb.embedded.glassfish.instance.root","./src/test/testing-domain");
              props.put("org.glassfish.ejb.embedded.glassfish.web.http.port","");
              props.put("javax.enterprise.system.container.web", "FINE");
              ec = EJBContainer.createEJBContainer(props);
              ctx = ec.getContext();
          }
      
          @AfterClass
          public static void closeContainer() throws Exception {
              if(ctx!=null)
                  ctx.close();
              if(ec!=null)
                  ec.close();
          }
      
          @Test
          public void shouldCreateABook() throws Exception {
              Book book = new Book();
              book.setTitle("The Hitchhiker's Guide to the Galaxy");
              book.setPrice(12.5F);
              book.setDescription("Scifi book created by Douglas Adams");
              book.setIsbn("1-84023-742-2");
              book.setNbOfPages(354);
              book.setIllustrations(false);
      
              Object obj = ctx.lookup("java:global/embedded-classes/BookLocalHomeBean");
              BookLocalHome bookHome = (BookLocalHome) obj;
              BookLocal bookLocal = bookHome.createBook(book);
              book = bookLocal.findBookById(book.getId());
              assertNotNull("Book should not be null", book);
              assertNotNull("ID should not be null", book.getId());
          }
      }
      

      The application I'm using is a modified version of embedded-glassfish-example:

      BookLocal.java
      package com.coverity.testsuite.ejb.localhome;
      
      import java.util.List;
      import javax.ejb.EJBLocalObject;
      
      import com.coverity.testsuite.ejb.Book;
      
      public interface BookLocal extends EJBLocalObject {
      
          void deleteBook(Book book);
      
          Book findBookById(Long id);
      
          List<Book> findBooks();
      
          Book updateBook(Book book);
      }
      
      BookLocalHome.java
      package com.coverity.testsuite.ejb.localhome;
      
      import javax.ejb.EJBLocalHome;
      import javax.ejb.CreateException;
      
      import com.coverity.testsuite.ejb.Book;
      
      public interface BookLocalHome extends EJBLocalHome {
      
          public BookLocal createBook(Book book) throws CreateException;
      }
      
      BookLocalHomeBean.java
      package com.coverity.testsuite.ejb.localhome;
      
      import java.util.List;
      import javax.ejb.Stateful;
      import javax.ejb.LocalHome;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      import javax.annotation.Resource;
      import javax.ejb.SessionContext;
      
      import com.coverity.testsuite.ejb.Book;
      
      /**
       *
       * @author pasc
       */
      @Stateful
      @LocalHome(BookLocalHome.class)
      public class BookLocalHomeBean {
          @PersistenceContext(unitName = "bookstore-ejb")
          EntityManager em;
      
          @Resource
          private SessionContext sessionContext;
      
          public List<Book> findBooks() {
              return em.createNamedQuery("Book.findAllBooks", Book.class).getResultList();
          }
      
          public Book findBookById(Long id) {
              return em.find(Book.class, id);
          }
      
          public void ejbCreateBook(Book book) {
              em.persist(book);
          }
      
          public void deleteBook(Book book) {
              em.remove(em.merge(book));
          }
      
          public Book updateBook(Book book) {
              return em.merge(book);
          }
      }
      

      The above code hasn't been validated another embedded EJB container, therefore it could be incorrect. It seems correct, based on looking at the JBoss quick start EJB test cases.

      I enabled logging within Glassfish by specifying the following in a .properties file:

      handlers= java.util.logging.ConsoleHandler
      java.util.logging.ConsoleHandler.level=FINEST
      com.sun.enterprise.naming.level=FINEST
      

      com.sun.enterprise.naming is specified in LogFacade, used as SerialContext's logger. Passing it to Maven as mvn clean test -Djava.util.logging.config.file=src/test/resources/customlogging.properties results in the nice stack trace, trimmed below:

      Apr 16, 2014 11:42:55 AM com.sun.enterprise.naming.impl.SerialContext <init>
      FINE: SerialContext ==> SerialContext instance created : SerialContext[myEnv={java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory,
        java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl,
        java.naming.factory.url.pkgs=com.sun.enterprise.naming}
      Apr 16, 2014 11:42:55 AM com.sun.enterprise.naming.impl.SerialContext <init>
      FINE: Serial Context initializing with process environment org.glassfish.api.admin.ProcessEnvironment@7a341251
      Apr 16, 2014 11:42:55 AM com.sun.enterprise.naming.impl.SerialContext lookup
      FINE: SerialContext ==> lookup( java:global/embedded-classes/BookLocalHomeBean)
      Apr 16, 2014 11:42:55 AM com.sun.enterprise.naming.impl.SerialContext lookup
      FINE: SerialContext ==> lookup relative name : java:global/embedded-classes/BookLocalHomeBean
      Apr 16, 2014 11:42:55 AM com.sun.enterprise.naming.impl.SerialContextProviderImpl lookup
      FINE:  SerialContextProviderImpl :: lookup java:global/embedded-classes/BookLocalHomeBean
      Apr 16, 2014 11:42:55 AM com.sun.enterprise.naming.impl.SerialContext lookup
      FINE: enterprise_naming.serialctx_communication_exception
      Apr 16, 2014 11:42:55 AM com.sun.enterprise.naming.impl.SerialContext lookup
      FINE:
      java.lang.NullPointerException
              at com.sun.ejb.containers.JavaGlobalJndiNamingObjectProxy.create(JavaGlobalJndiNamingObjectProxy.java:65)
              at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:507)
              at com.sun.enterprise.naming.impl.SerialContext.lookup(SerialContext.java:455)
      

      Kicking up Eclipse and debugging the app showed the following:

      com.sun.ejb.containers.JavaGlobalJndiNamingObjectProxy.create(Context)
      /*    */   public Object create(Context ic) {
      /* 63 */     GenericEJBLocalHome genericLocalHome = this.container.getEJBLocalBusinessHome(this.intfName);
      /*    */     
      /* 65 */     return genericLocalHome.create(this.intfName);
      /*    */   }
      
      • this.intfName = com.coverity.testsuite.ejb.localhome.BookLocalHome
      • genericLocalHome = null

      null is being passed into the create call, which triggers the NPE. Looking into `getEJBLocalBusinessHome`:

      com.sun.ejb.containers.BaseContainer.getEJBLocalBusinessHome(String)
      /*      */   public final GenericEJBLocalHome getEJBLocalBusinessHome(String clientViewClassName)
      /*      */   {
      /* 1016 */     return isLocalBeanClass(clientViewClassName) ? this.ejbOptionalLocalBusinessHome : this.ejbLocalBusinessHome;
      /*      */   }
      
      • clientViewClassName = com.coverity.testsuite.ejb.localhome.BookLocalHome
      • this.ejbOptionalLocalBusinessHome = null
      • this.ejbLocalBusinessHome = null

      Both are null. Let's see which one should have been picked:

      com.sun.ejb.containers.BaseContainer.isLocalBeanClass(String)
      /*      */   boolean isLocalBeanClass(String className)
      /*      */   {
      /* 1023 */     return (this.hasOptionalLocalBusinessView) && ((className.equals(this.ejbClass.getName())) || (className.equals(this.ejbGeneratedOptionalLocalBusinessIntfClass.getName())));
      /*      */   }
      
      • this.hasOptionalLocalBusinessView = false
      • className == com.coverity.testsuite.ejb.localhome.BookLocalHome
      • this.ejbClass.getName() == com.coverity.testsuite.ejb.localhome.BookLocalHomeBean
      • this.ejbGeneratedOptionalLocalBusinessIntfClass.getName() == null

      isLocalBeanClass returns false. Assuming the bug isn't lurking above, this.ejbLocalBusinessHome should not be null.

      Now, BaseContainer is the abstract class. Eclipse is telling me the actual type is com.sun.ejb.containers.StatefulSessionContainer. StatefulSessionContext doesn't set the field ejbLocalBusinessHome. Grepping around ejbLocalBusinessHome is set in BaseContainer:

      com.sun.ejb.containers.BaseContainer.initializeHome()
      /* 1444 */       if (this.hasLocalBusinessView) {
      /* 1445 */         this.ejbLocalBusinessHomeImpl = instantiateEJBLocalBusinessHomeImpl();
      /*      */         
      /* 1447 */         this.ejbLocalBusinessHome = ((GenericEJBLocalHome)this.ejbLocalBusinessHomeImpl.getEJBLocalHome());
      /*      */       
      

      To get to that point, isLocal} needs to be true, which it is in this run. For the field to be set, {{this.hasLocalBusinessView needs to be true, which it isn't in this run. BaseContainer sets it only in one spot. (StatefulSessionContainer does reference it; it just doesn't set it.):

      com.sun.ejb.containers.BaseContainer.BaseContainer(ContainerType, EjbDescriptor, ClassLoader)
      /*  650 */         if (this.ejbDescriptor.isLocalBusinessInterfacesSupported()) {
      /*  651 */           this.isLocal = true;
      /*  652 */           this.hasLocalBusinessView = true;
      
      • this.ejbDescriptor = com.sun.enterprise.deployment.EjbSessionDescriptor

      And digging into isLocalBusinessInterfacesSupported:

      com.sun.enterprise.deployment.EjbAbstractDescriptor.isLocalBusinessInterfacesSupported()
      /*     */   public boolean isLocalBusinessInterfacesSupported()
      /*     */   {
      /* 278 */     return this.localBusinessClassNames.size() > 0;
      /*     */   }
      
      • this.ejbDescriptor.localBusinessClassNames == []

      Well, shucks. localBusinessClassNames is set in a ctor and also com.sun.enterprise.deployment.EjbAbstractDescriptor.addLocalBusinessClassName(String):

      com.sun.enterprise.deployment.EjbAbstractDescriptor.addLocalBusinessClassName(String)
      /*     */   public void addLocalBusinessClassName(String className) {
      /* 185 */     this.localBusinessClassNames.add(className);
      /*     */   }
      

      Eclipse is saying it's only called by org.glassfish.ejb.deployment.annotation.handlers.AbstractEjbHandler.setBusinessAndHomeInterfaces(EjbDescriptor, AnnotationInfo), whoop. Debugging that method shows this:

      org.glassfish.ejb.deployment.annotation.handlers.AbstractEjbHandler.setBusinessAndHomeInterfaces(EjbDescriptor, AnnotationInfo)
      ...
      /* 452 */     if (localBusIntfs.size() > 0) {
      /* 453 */       for (Class next : localBusIntfs) {
      /* 454 */         ejbDesc.addLocalBusinessClassName(next.getName());
      /*     */       }
      /*     */     }
      

      And since the EJB bean doesn't have any business interfaces implemented, (as defined by JSR), localBusIntfs is null. From JSR 318:

      While it is expected that the bean class will typically implement its business inter- face(s), if the bean class uses annotations or the deployment descriptor to designate its business interface(s), it is not required that the bean class also be specified as imple- menting the interface(s).

      In my test case the EJB doesn't implement the interface. But the only way the logic above would not cause the NPE is if a business interface is found. To me that seems like a bug in Glassfish. I'd think the Local interface would have been returned. I dunno what the right thing is, though. Assuming the code above is valid, then yeah, it's a bug.

        Activity

        There are no comments yet on this issue.

          People

          • Assignee:
            Srini
            Reporter:
            jonpasski
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated: