Tomcat/WebSphere Integration

I just went through a devil of a time trying to get JSPs in Tomcat talking to EJBs in WebSphere. (Click here for my reasons for requiring this connectivity.) The stateless session beans use an Oracle datasource to access the underlying data. It wasn't a matter of not having the application JARs in the CLASSPATH; I have quite a bit of experience in that area. It was instead a number of little things, the "gotcha" type of issues which made my life miserable. I invite you to walk through it with me.

I've developed some tools which generate EJBs from a table which is in turn generated from the SQL code used to create the database tables. Information about keys and the like is extracted so that operations like delete use the requisite parameter count. I've also added the ability to extend the generated beans, including files from a separate subdirectory at various points in the code.

One of the limitation of entity beans is that (prior to EJB 2.1) you don't have the ability to specify the ORDER BY clause in the EJB-QL (EJB Query Language, a subset of SQL). Session beans are more verbose than their entity counterparts since all of the JDBC code has to be included. Then again, by using "standard" SQL and javax.sql.DataSource you can attain vendor independence as well as take advantage of the commonly-available JDBC connection pool drivers.

I actually go one step further: the name of the data source is obtained from the bean environment (java:comp/env prefix for the context lookup) in order to make deployment easier. You could even plug in XA data sources to permit distributed transactions with two-phase commit. The idea here is that you want to develop with maximum flexibility in mind. In a number of circumstances that means session rather than entity beans.

In order to make debugging easier, I try to test functionality in layers, working up from the lowest to the highest (application layer). I wrote a simple, stand-alone Java application which would perform a lookup of the EJB, obtain a reference and try to invoke a method on the object. Here's the code:

	Properties props = new Properties();
	props.put( Context.PROVIDER_URL, "iiop://localhost" );
	props.put( Context.INITIAL_CONTEXT_FACTORY,
	  "com.ibm.websphere.naming.WsnInitialContextFactory" );
	Context context = new InitialContext( props );
	Object obj = context.lookup( "sudsy/Skill" );
	SkillHome skillHome = (SkillHome)
	  javax.rmi.PortableRemoteObject.narrow( obj, SkillHome.class );
	Skill skillBean = skillHome.create();
	Vector results = skillBean.listSkillIdSkillDesc();
None of this is rocket science; I've done this any number of times. What was new was receiving the following error:
javax.naming.ServiceUnavailableException: NULL returned when resolving
initial reference=NameService
This was followed by a stack trace which did nothing to steer me in the direction of a solution. I did searches on Google (used to be DejaNews) and couldn't find the answer there. I ftp'd the code over to a different server and lo-and-behold it worked perfectly. Now it was just a matter of trying to determine what was different between the two environments. It turns out that the first server was using the 1.3.1_01 HotSpot virtual machine while the second was using the 1.3.0 Classic VM.

The solution to the problem was simple: add the WebSphere java/bin directory to the beginning of the PATH environment variable. I don't know how long it would have taken me to arrive at the solution if I didn't have access to multiple servers! This is precisely the kind of problem we don't need when we're touting portability as the forté of the new generation of applications. Pehaps the answer lies hidden somewhere in the IBM support site but I wasn't able to find it there or anywhere else.

One item of interest that I uncovered in my searches was a suggestion that Tomcat fires up a naming service of its own. Thinking that it might have been a case of the Tomcat naming service responding instead of the WebSphere one, I went back to documentation mining in order to determine how to disable the Tomcat service. While not a criticism directed at the people who've worked hard on Tomcat, the documentation is spotty. Trying to find anything can be an exercise in frustration.

So I took a different tack. There were some hints that it would be possible to modify the WebSphere server-cfg.xml file to change the port number of the naming service. Some more mining and examining of the file and I found this line:

<orbSettings xmi:id="ORBConfig_1" enable="true" bootstrapHost="localhost" bootstrapPort="900">
The bootstrapHost controls the interface binding (the loopback in this case) and the bootstrapPort is the naming service listener port. I was able to change this to 9001 and continued testing. It didn't solve the problem but I'm including it here in case you ever need to make the change.

With the command line application working, it was time to focus on the Tomcat environment. Since lookups are the most expensive operation when using EJBs, I had the notion to perform the lookups at initialization and store references to the home interfaces in the application scope. First we have to modify the WEB-INF/web.xml file to indicate the class which should be loaded at startup. Here's the corresponding section of the file:

<servlet id="InitServlet">
   <servlet-name>ResumeInit</servlet-name>
   <servlet-class>ResumeInit</servlet-class>
   <load-on-startup>1</load-on-startup>
</servlet>
The ResumeInit.class file has to reside in the WEB-INF/classes directory in this case. I should probably create a subdirectory for containing this code, similar to the one for the JSP tags. I'll leave that for a later time. Now we can look at the code:
import	java.util.Properties;
import	javax.naming.InitialContext;
import	javax.naming.Context;
import	javax.naming.NamingException;
import	java.rmi.RemoteException;
import	javax.ejb.EJBException;
import	net.sudsy.SkillHome;
import	net.sudsy.ProfileSkillXrefHome;
import	javax.servlet.jsp.PageContext;
import	javax.servlet.Servlet;
import	javax.servlet.ServletConfig;
import	javax.servlet.ServletContext;
import	javax.servlet.ServletRequest;
import	javax.servlet.ServletResponse;

public class ResumeInit implements Servlet {

    private ServletConfig	conf;

    public void init( ServletConfig conf ) {
    	this.conf = conf;
    	ServletContext	ctx = conf.getServletContext();
    	try {
    		Object	obj = null;

    		Properties props = new Properties();
    		props.put( Context.PROVIDER_URL, "iiop://localhost:9001" );
    		props.put( Context.INITIAL_CONTEXT_FACTORY,
    		  "com.ibm.websphere.naming.WsnInitialContextFactory" );
    		Context context = new InitialContext( props );
    		obj = context.lookup( "sudsy/Skill" );
    		SkillHome skillHome = (SkillHome)
    		  javax.rmi.PortableRemoteObject.narrow( obj,
    		  SkillHome.class );
    		ctx.setAttribute( "skillHome", skillHome );
    		obj = context.lookup( "sudsy/ProfileSkillXref" );
    		ProfileSkillXrefHome skillXrefHome = (ProfileSkillXrefHome)
    		  javax.rmi.PortableRemoteObject.narrow( obj,
    		  ProfileSkillXrefHome.class );
    		ctx.setAttribute( "profileSkillXrefHome",
    		  skillXrefHome );
    	}
    	catch( Exception e ) {
    		e.printStackTrace();
    	}
    }

    public ServletConfig getServletConfig() {
    	return( conf );
    }

    public String getServletInfo() {
    	return( "Initialization servlet for resume application" );
    }

    public void service( ServletRequest req, ServletResponse resp ) {
    }

    public void destroy() {
    }
}
There are a couple of items of interest here. First, note that I've changed the URI for the naming service to point to port 9001. I mentioned earlier how I had changed this in the server-cfg.xml and this code demonstrates how the URI has to be modified. Secondly, this class implements javax.servlet.Servlet and so must implement all of the methods defined therein. Since this code is never meant to be called directly (no servlet-mapping in web.xml) we use an empty service method.

One of the things I was trying to accomplish was the ability to have the custom tags retrieve the home references from the application scope. Since the tags extend javax.servlet.jsp.tagext.BodyTagSupport, the obvious mechanism would be to use something like this:

Object ref = pageContext.getAttribute( "skill", PageContext.APPLICATION_SCOPE );
The question was how do I store the references when I'm implementing the javax.servlet.Servlet interface? It makes sense when you spend a bit of time thinking and reading the javadocs. The setAttribute method of the javax.servlet.Servlet interface accomplishes the task.

Restarting Tomcat caused a ClassNotFoundException to be thrown by the ResumeInit class. By now I'm starting to get the hang of this! The documentation states that you can put the jars required by your application into the WEB-INF/lib directory. Rather than copying everything over I just created symbolic links back to the original files. Restarting Tomcat again produced exactly the same error.

Sensing that I was so close to victory, I revisited the Tomcat documentation and also did some digging in the directory structure. There's a README file in the ${TOMCAT_HOME}/lib/apps directory which indicates that jars for all applications should be stored in that directory. I created the symbolic links and tried again: success! Just so others don't have to go through the trial-and-error phase, here's a long listing of that directory:

-rw-r--r--    1 pselby   users          91 Mar 26  2002 README
lrwxrwxrwx    1 pselby   users          31 Nov 25 20:12 activation.jar -> /home/pselby/lib/activation.jar
lrwxrwxrwx    1 pselby   users          29 Nov 25 20:12 classes111.zip -> /usr/local/lib/classes111.zip
lrwxrwxrwx    1 pselby   users          27 Nov 25 20:12 common.jar -> /home/pselby/lib/common.jar
lrwxrwxrwx    1 pselby   users          44 Nov 25 20:12 dt.jar -> /opt/WebSphere/AppServer/java/lib/dt.jar
lrwxrwxrwx    1 pselby   users          41 Nov 25 20:12 j2ee.jar -> /opt/WebSphere/AppServer/lib/j2ee.jar
lrwxrwxrwx    1 pselby   users          30 Nov 25 20:12 jce1_2-do.jar -> /home/pselby/lib/jce1_2-do.jar
lrwxrwxrwx    1 pselby   users          25 Nov 25 20:12 jndi.jar -> /home/pselby/lib/jndi.jar
lrwxrwxrwx    1 pselby   users          33 Nov 25 20:12 jsdk.jar -> /home/pselby/JSDK2.0/lib/jsdk.jar
lrwxrwxrwx    1 pselby   users          25 Nov 25 20:12 mail.jar -> /home/pselby/lib/mail.jar
lrwxrwxrwx    1 pselby   users          39 Nov 25 20:12 ns.jar -> /opt/WebSphere/AppServer/lib/ns.jar
lrwxrwxrwx    1 pselby   users          25 Nov 25 20:12 oboe.jar -> /home/pselby/lib/oboe.jar
lrwxrwxrwx    1 pselby   users          33 Nov 25 20:12 providerutil.jar -> /home/pselby/lib/providerutil.jar
lrwxrwxrwx    1 pselby   users          47 Nov 25 20:12 tools.jar -> /opt/WebSphere/AppServer/java/lib/tools.jar
lrwxrwxrwx    1 pselby   users          40 Nov 25 20:12 ujc.jar -> /opt/WebSphere/AppServer/lib/ujc.jar
I'm not suggesting that you'll actually need all of these but my environment is such that I'm running things like the Java Cryptography Extensions and so I basically worked backwards from my CLASSPATH. I don't want or need to prune the files until I find the minimum subset. Missing from this listing is the link to the output from the assembly.sh application. You'll obviously need that in order to access your home-grown beans.

So what does the code which uses these references look like? Here's a code snippet from the first custom tag on the page:

    public int doStartTag() throws JspException {
        Vector skills = null;
    		...

    	skills = (Vector) pageContext.getAttribute( "skills",
    	  PageContext.SESSION_SCOPE );
    	if( skills == null ) {
    		skills = loadSkills();
    		pageContext.setAttribute( "skills", skills,
    		  PageContext.SESSION_SCOPE );
    	}
    		...

    }

    private Vector loadSkills() throws JspException {
    	SkillHome	home = null;
    	Skill		bean = null;

        home = (SkillHome) pageContext.getAttribute( "skillHome",
	  PageContext.APPLICATION_SCOPE );
    	try {
    		bean = home.create();
    		return( bean.listSkillIdSkillDesc() );
    	}
    	catch( Exception e ) {
    		String	msg = e.getMessage();
    		if( msg == null )
    			msg = e.toString();
    		throw( new JspException( getClass().getName() + ": " +
    		  msg ) );
    	}
    }
Since this particular page is reentrant, we first check to see if the skills vector is already available in session scope. If not, we invoke loadSkills and store the returned vector in session scope. The loadSkills method retrieves the home reference from the application scope and invokes create which provides a remote interface reference. We can then invoke methods on that interface.

So now we have a fairly robust implementation. Requests for JSP pages are received by Apache and handed off to Tomcat. The custom tags in these pages obtain remote references to stateless session EJBs. Those beans use a javax.sql.DataSource named in the servlet environment. That data source uses a connection pool to access the information in the Oracle database.

Update

In hindsight it was my own fault. I had originally configured my webserver to use appropriate document roots in Apache by using the <Virtual Host> directives. As I was writing this article I decided that, rather than have the JSPs for sudsy.net under a single directory, it would make sense to create the WEB-INF structure in each of many subdirectories. My original httpd.conf file contained the following lines:
NameVirtualHost 216.221.84.254

<VirtualHost www.sudsy.net:80>
    SSLEngine off
    ServerName www.sudsy.net
    DocumentRoot "/u/website/sudsy"
</VirtualHost>
    ...

Include /home/pselby/apache/jakarta-tomcat-3.3.1/conf/jserv/tomcat.conf
The tomcat.conf file contains directives to the mod_jserv modules and contains the following:
    ....

AddType text/jsp .jsp
AddHandler jserv-servlet .jsp
    ....

ApJServMount /servlet /root
    ....
The first part routes all requests for files with the .jsp suffix to Tomcat. The second routes any requests which start with /servlet to the same destination. Finally, the ${TOMCAT_HOME}/conf/apps-216.221.84.254.xml file contained (in part) the following:
<Host name="www.sudsy.net" appBase="/u/website/sudsy">
    <Context path="" docBase="/u/website/sudsy"/>
</Host>
I decided to move the directory /u/website/sudsy/WEB-INF to /u/website/sudsy/technology/WEB-INF. I also took the opportunity to modify the web.xml file to map the taglib URIs to the files in the new WEB-INF/tlds directory. It merely required adding the following lines:
   <taglib>
      <taglib-uri>resume</taglib-uri>
      <taglib-location>/WEB-INF/tlds/resume.tld</taglib-location>
   </taglib>
The JSPs were working properly in short order after changing ${TOMCAT_HOME}/conf/apps-216.221.84.254.xml to read thusly:
<Host name="www.sudsy.net" appBase="/u/website/sudsy/technology">
    <Context path="/technology" docBase="/u/website/sudsy/technology"/>
</Host>
The problem was that I had a servlet which had also moved and was no longer accessible. The solution was to add the following line to the ${TOMCAT_HOME}/conf/jserv/tomcat.conf file:
ApJServMount /technology/servlet /root
My mistake was in assuming that the /servlet mount would be suffixed to the document base, namely /u/website/sudsy/technology but it's the adapter (in Apache) which needs to know the right mapping. Of course it's obvious when you have the benefit of 20/20 hindsight but it took me a little while to get everything figured out. Then again, as far as I know this is the only place on the web where you'll find this information.

Summary

Some might ask "why such a convoluted architecture?" That's a reasonable question since I could have accomplished almost everything in WebSphere. It will serve up HTML and JSP pages, is J2EE compliant and supports EJBs. It's just not always the most efficient solution. Even the documentation for Tomcat warns that it is not as efficient at serving straight HTML pages as Apache. Click here for an editorial in which I discuss various architectures.

I'm a big believer in using the right tool for the job. The Apache webserver is blindingly fast and it's a very mature platform. If you need to serve JSPs then Tomcat is the reference implementation. You can create custom tags, beans and servlets, any or all of which can use the usual extensions, e.g. JDBC. Both of these open-source products are freely available. For industrial-strength applications with EJBs and transactional integrity, you need a J2EE server like WebSphere. At $35K for the Advanced Single Server edition for linux, they don't come cheap.

For the database back-end there are the PostgreSQL and MySQL platforms, both open-source and so freely available. I've always been a big fan of DB/2 for rock-solid reliability. If your business simply cannot afford any data loss ever, it's an excellent choice. Others swear by Oracle and there are still people running Sybase, Informix, Ingres as well as a host of specialty servers. Database systems provide powerful features which permit concurrent access by hundreds or thousands of clients. But these capabilities come with a hefty price tag.

There's a wide spectrum of products and capabilities available at various price points. What I wanted to achieve was a demonstration of the various elements as part of a cohesive whole. The architecture is also extensible. For example, rather than just caching the EJB home references I could create a resource pool of bean references. Since I'm using stateless session beans there's no state to maintain: each operation is atomic and independent. I could maintain references from more than one server and provide some rudimentary load-balancing.

I could also easily partition the application into tiers with the webservers communicating via AJP12/13 to the Tomcat servers. Those could then communicate via IIOP to the WebSphere servers which might then interface to a high-availability cluster running DB/2 or Oracle. The possible combinations and permutations are endless. At the end of the day, you just want the applications to work. What better way to prove interoperability than combining all these disparate elements and showing that it all actually does work as advertised?

Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 by Phil Selby. All rights reserved.