6/11/2011

Tomcat to GlassFish Interoperability: Remote EJB Invocation

In order to make Tomcat(7.0.8)-to-GlassFish(v3) remote EJB invocation, one has to solve 2 challenges:

1, add relevent GlassFish client-side jar files to tomcat lib. There is an all-inclusive client jar (glassfish/lib/gf-client.jar), which references practically all GlassFish jars. But adding gf-client.jar to Tomcat lib would cause various conflict of system classes. So one still needs to figure out which GlassFish jars are needed, mostly by trial and error.

For a simple Tomcat-GlassFish test that includes a servlet (in Tomcat) invoking remote EJB 3 stateless bean (in GlassFish 3), the following 32 jars are the minimum set of jars to be included in Tomcat lib:

javax.ejb.jar
ejb-container.jar
deployment-common.jar
dol.jar
glassfish-corba-csiv2-idl.jar
glassfish-corba-codegen.jar
ssl-impl.jar
security.jar
ejb.security.jar
management-api.jar
gmbal-api-only.jar
gmbal.jar
glassfish-corba-asm.jar
glassfish-corba-newtimer.jar
glassfish-corba-orbgeneric.jar
bean-validator.jar
config-types.jar
kernel.jar
config.jar
config-api.jar
glassfish-corba-omgapi.jar
glassfish-corba-orb.jar
orb-connector.jar
orb-enabler.jar
orb-iiop.jar
glassfish-api.jar
auto-depends.jar
hk2-core.jar
internal-api.jar
common-util.jar
glassfish-corba-internal-api.jar
glassfish-naming.jar

2, add GlassFish jndi properties to application lookup code. The complete servlet class:

package test;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.naming.*;
import javax.annotation.*;

@javax.servlet.annotation.WebServlet(urlPatterns = "/*")
public class TestServlet extends HttpServlet {
private static final String glassfishJndiPropertiesPath = "/glassfish-jndi.properties";
private static final String testBeanJndi = "test.TestIF";
private TestIF testBean;

private Properties getGlassFishJndiProperties() {
Properties props = new Properties();
try {
props.load(getClass().getResourceAsStream(glassfishJndiPropertiesPath));
} catch (IOException e) {
System.out.println("Failed to load " + glassfishJndiPropertiesPath);
}
System.out.println("Got glassfish-jndi.properties: " + props);
return props;
}

@PostConstruct
private void initTestBean() {
try {
InitialContext ic = new InitialContext(getGlassFishJndiProperties());
testBean = (TestIF) ic.lookup(testBeanJndi);
} catch (NamingException e) {
throw new RuntimeException(e);
}
}

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println("testBean.hello(): " + testBean.hello());
}

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}
The content of the test.war deployed in Tomcat:
WEB-INF/classes/glassfish-jndi.properties
WEB-INF/classes/test/TestIF.class
WEB-INF/classes/test/TestServlet.class
TestIF is the remote business interface of TestBean. WEB-INF/classes/glassfish-jndi.properties is packaged into the war file to provide GlassFish jndi bootstrap properties. Its content is basedon the one in GlassFish server:
java.naming.factory.initial=com.sun.enterprise.naming.impl.SerialInitContextFactory
java.naming.factory.url.pkgs=com.sun.enterprise.naming
# Required to add a javax.naming.spi.StateFactory for CosNaming that
# supports dynamic RMI-IIOP.
java.naming.factory.state=com.sun.corba.ee.impl.presentation.rmi.JNDIStateFactoryImpl

# uncomment the following lines if GlassFish is running on
# different host and non-default port number.
# org.omg.CORBA.ORBInitialHost=localhost
# org.omg.CORBA.ORBInitialPort=3700
test-ejb.jar deployed in GlassFish 3 just contains the bean class and its remote business interface:
test/TestBean.class
test/TestIF.class
TestIF.java:
package test;
import javax.ejb.*;

@Remote
public interface TestIF {
public String hello();
}
TestBean.java:
package test;
import javax.ejb.*;

@Stateless
@Remote(TestIF.class)
public class TestBean implements TestIF {
public String hello() {
return "From " + this;
}
}
After both test-ejb.jar and test.war are packaged, first deploy test-ejb.jar to GlassFish, and then deploy test.war to Tomcat:
cp test-ejb.jar $GLASSFISH_HOME/domains/domain1/autodeploy

# to verify it has been successfully deployed:
$GLASSFISH_HOME/bin/asadmin list-applications

cp test.war tomcat/webapps
Visit the test URL http://localhost:7070/test/ to see the result of remote EJB invocation. The default http port number in Tomcat and GlassFish are the same (8080). So if they are on the same host, need to change one of them. I changed Tomcat to use 7070 by editing tomcat/conf/server.xml.

When loading glassfish-jndi.properties, the current class loader, instead of ServletContext, is used to get the resource. The reason is in @PostConstruct method, ServletConfig has not been initialized, and so GenericServlet.getServletConfig().getServletContext() failed with NullPointerException.

The jndi name for looking up, test.TestIF, is the default GlassFish-specific global jndi name. The portable global jndi name, java:global/test-ejb/TestBean, works as well.

2 comments: