0
votes

I have a working Glassfish 5 setup inside of Eclipse.

I have modules set up like this:

common

  • contains remote interface for singleton bean with @remote and @local annotation
  • included in all other modules as a pom dependency

core

  • contains a singleton bean that implements the interface in common
  • intended to contain service classes stored in JNDI that allow any number of clients to lookup and store data and/or access other service logic
  • builds a war file that deploys onto GlassFish

desktop

  • does an InitialContext lookup of the singleton bean. (I intend to abstract this into the Service Lookup design pattern).
  • intended to be a desktop client that remotely accesses beans stored on the server.
  • Right now, I'm just trying to get it to remotely access the service in core and print some text to the console proving it worked.

I think my problem stems from a misunderstanding of how to store a custom bean in JNDI in the first place. I looked on Google, but I mostly find articles and answers to questions telling me to add name and mappedName to the Singleton annotation or they only show how to add a predefined bean into JDNI such as a Boolean or String, and not something custom defined.

My bean is defined like this:

@Singleton(name = "BookService", mappedName = "ejb/BookService")
public class BookServiceImpl implements BookService {

    private static final long serialVersionUID = 1L;

which results in this on the Glassfish server: Core Server Screenshot

but nothing appears in JNDI: JNDI Screenshot

The client does it's InitialContext lookup like this (I've tried multiple ways of writing the JNDI name):

BookService bookService = (BookService) initialContext.lookup("java:global/core/BookService");

using these configurations (I defined my domain with a base port of 8000, so every port is 8000 + something):

glashfishEnvironmentTable = new Hashtable<String, String>();
glashfishEnvironmentTable.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.enterprise.naming.impl.SerialInitContextFactory");
glashfishEnvironmentTable.put(Context.STATE_FACTORIES,
            "com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
glashfishEnvironmentTable.put(Context.URL_PKG_PREFIXES, "com.sun.enterprise.naming");
//glashfishEnvironmentTable.put(Context.PROVIDER_URL, "ejbd://localhost:8080/");
// on a different host than the appserver   
glashfishEnvironmentTable.put("org.omg.CORBA.ORBInitialHost", "localhost");
// optional.  Defaults to 3700.  Only needed if target orb port is not 3700.
glashfishEnvironmentTable.put("org.omg.CORBA.ORBInitialPort", "8037");

I get an error like this when I run it:

Exception in thread "main" javax.naming.CommunicationException: Communication exception for SerialContext[myEnv={org.omg.CORBA.ORBInitialPort=8037, java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory, org.omg.CORBA.ORBInitialHost=localhost, java.naming.factory.url.pkgs=com.sun.enterprise.naming, java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl} [Root exception is java.rmi.MarshalException: CORBA MARSHAL 1330446343 No; nested exception is: 
org.omg.CORBA.MARSHAL: FINE: 00810007: Underflow in BufferManagerReadStream after last fragment in message  vmcid: OMG  minor code: 7  completed: No]

I think my InitialContext is configured properly because when I manually define the bean in JNDI like this (The ServiceFactory doesn't do anything, because I don't know how to properly extend ObjectFactory):

Bean defined in JNDI

I get this:

Exception in thread "main" java.lang.ClassCastException: javax.naming.Reference cannot be cast to common.service.BookService

Is there simple fix to this I am missing, or am I mixing oil and water trying to get an EJB Singleton into JNDI? Or am I missing something big like an EJBHome or a Servlet?

Do I need to extend the factory class to add my service classes into JDNI, or should ObjectFactory work? If I must extend the factory class, how would I go about it?

I hope I've defined the scope of the question well enough, but this is all new to me, so if anybody has experience implementing something like this, I appreciate any input that gets me closer to doing it right.

1
Are you sure your EJB remote is accessible on java:global/core/BookService? Look into the server.log when deploying, it should list the JNDI names on which you can lookup the remote interface. Apart from that you do not need to do anything special to get the EJB into the JNDI. This will be done automatically when deploying.Christoph John
@ChristophJohn I just looked into the logs and found this: 2020-01-19T17:13:51.360-0500|Info: Portable JNDI names for EJB BookService: [java:global/core/BookService!common.service.BookService, java:global/core/BookService] I made sure I have the right name and tried again. I still get the CommunicationException.dboggs95
Of which data types are your variables in your BookServiceImpl? The BufferUnderflow error suggests that some of them (or your EJB) are not implementing the Serializable interface. Also see here: stackoverflow.com/questions/7751951/…Christoph John
@ChristophJohn Thanks for helping me look into this. I've actually discovered what I did wrong and posted a detailed solution below.dboggs95

1 Answers

1
votes

I figured it out. I did two things I shouldn't have.

  1. I forgot to maven install my common module before building my war file... doh!
  2. It seems that the @Remote and the @Local annotations cannot be in the same interface or it will fail (which is a shame, because I was trying to set it up so I didn't have to have two nearly identical interfaces).

Here are some more details in case someone else has a similar issue and needs to see something that works (I haven't done @Local here, because I haven't tried it yet. It should be trivial to add though):

  1. For remote execution, you need an interface annotated with @Remote that extends Serializable (Serialization is required for Client/Server interface).
import java.io.Serializable;

public interface Remote extends Serializable{

}
import javax.ejb.Remote;

@Remote
public interface BookServiceRemote extends Remote {

    public String readBook();

}
  1. Your war file needs to contain the BookServiceImpl
import javax.ejb.Singleton;

import us.boggs.template.common.service.BookServiceRemote;

@Singleton(name = "BookService")
public class BookServiceImpl implements BookServiceRemote {

    private static final long serialVersionUID = 1L;

    @Override
    public String readBook() {
        return "Read the book.";
    }
}
  1. Your client pom.xml must include the common module and this:
<dependency>
 <groupId>org.glassfish.main.appclient</groupId>
 <artifactId>gf-client</artifactId>
 <version>5.1.0</version>
</dependency>
  1. Your client main method can use this by creating an InitialContext and doing a lookup(My base port was 8000, so my ORBInitialPort is 8000+37=8037):
private static Hashtable<String, String> glashfishEnvironmentTable;

static {
    glashfishEnvironmentTable = new Hashtable<String, String>();
    glashfishEnvironmentTable.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.enterprise.naming.impl.SerialInitContextFactory");
    glashfishEnvironmentTable.put(Context.STATE_FACTORIES,
                "com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl");
    glashfishEnvironmentTable.put(Context.URL_PKG_PREFIXES, "com.sun.enterprise.naming"); 
    glashfishEnvironmentTable.put("org.omg.CORBA.ORBInitialHost", "localhost");
    // optional.  Defaults to 3700.  Only needed if target orb port is not 3700.
    glashfishEnvironmentTable.put("org.omg.CORBA.ORBInitialPort", "8037");
}
InitialContext initialContext = new InitialContext(glashfishEnvironmentTable);
        BookServiceRemote bookService = (BookServiceRemote) initialContext.lookup("java:global/core/BookService");
System.out.println(bookService.readBook());