6/20/2011

Jetty to GlassFish Interoperability: Remote EJB Invocation

Jetty-to-GlassFish EJB invocation is similar to Tomcat-to-GlassFish case. I tested with Jetty Hightide 7.4.2 and GlassFish 3.1.

Jetty configuration (in csh/tcsh syntax):

setenv JETTY_HOME $HOME/tools/jetty
setenv JETTY_PORT 7070 # avoid port conflict
mkdir $JETTY_HOME/lib/ext/glassfish
The following commands copy 32 GlassFish jars needed for this test to Jetty directory:
cd $GLASSFISH_HOME/modules
cp 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 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 bean-validator.jar $JETTY_HOME/lib/ext/glassfish
I had to slightly modify TestServlet to make it work in Jetty:

1, declare TestServlet with web.xml instead of using @javax.servlet.annotation.WebServlet, probably because servlet 3.0 support is not there yet in Jetty 7.4.2;

2, change @PostConstruct method to servlet init method. When using @PostConstruct to look up TestBean, it is not initialized and produced NullPointerException at request time. So I changed it to servlet init() method, which is a lifecycle method since early days of servlet. The complete TestServlet.java and web.xml:
package test;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.naming.*;
import javax.annotation.*;

public class TestServlet extends HttpServlet {
private static final String glassfishJndiPropertiesPath = "/glassfish-jndi.properties";
private static final String testBeanJndi = "java:global/test-ejb/TestBean";
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;
}

public void init() {
try {
InitialContext ic = new InitialContext(getGlassFishJndiProperties());
testBean = (TestIF) ic.lookup(testBeanJndi);
System.out.println("Looked up " + testBeanJndi + ", got " + testBean);
} 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);
}
}

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>test.TestServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
After packaging, test2.war (a sample app test.war is already included in jetty/webapps, so use a different name) contains the following entries:
WEB-INF/classes/glassfish-jndi.properties
WEB-INF/classes/test/TestIF.class
WEB-INF/classes/test/TestServlet.class
WEB-INF/web.xml
Other files, TestIF, TestBean, test-ejb.jar and glassfish-jndi.properties, are all the same as in Tomcat and Resin exercise.

6/16/2011

Resin to GlassFish Interoperability: Remote EJB Invocation

Resin-to-GlassFish EJB invocation is almost identical to Tomcat-to-GlassFish case. I tested with Resin-4.0.18 with GlassFish 3.1. One minor difference is that Resin has its own bean validation implementation, so GlassFish's bean-validator.jar need not be copied to Resin. The following is the list of 31 GlassFish jars needed for Resin client, in one single line for copying:

cd $GLASSFISH_HOME/modules
cp 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 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 $RESIN_HOME/lib
To change Resin http port number from the default 8080 to other number (e.g., 7070), edit resin/conf/resin.xml:
<http address="*" port="7070"/>
All the rest, including test app code, deployment & execution steps are exactly the same as in Tomcat exercise. This is the test command & output in resin:
curl http://localhost:7070/test/
testBean.hello(): From test.TestBean@47dccc2

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.

6/10/2011

javax.naming.Reference cannot be cast to in GlassFish

This error usually happens when the client tries to cast the lookup result to the expected type, such as EJB remote interface. This is what happens behind the scene. During the naming context lookup, a javax.naming.Reference is first acquired, which contains information how to create the actual object for the client. If the object creation step fails, then the half-baked javax.naming.Reference is returned to the client, hence the ClassCastException.

Some reasons why the naming manager failed to create object:

1, the object factory class responsible for creating the object is not available in the client classpath. Think of javax.naming.Reference as the raw material for creating the object. When there is no factory, no object is created, and the raw material is thrown back to the client.

In GlassFish 3, the object factory for remote EJB reference appears to be com.sun.ejb.containers.RemoteBusinessObjectFactory, packaged in ejb-container.jar. So ejb-container.jar and javax.ejb.jar should be in the client classpath by direct inclusion or by reference, along with a series of other jar files.

The following is the toString format of the returned javax.naming.Reference:

Reference Class Name: test.TestIF
Type: url
Content: ejb/TestBean__3_x_Internal_RemoteBusinessHome__
2, the object factory is in client classpath, but not loadable by the current thread context class loader.

The algorithm for creating object is in javax.naming.spi.NamingManager.