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(); |
javax.naming.ServiceUnavailableException: NULL returned when resolving initial reference=NameService |
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"> |
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> |
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() {
}
}
|
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 ); |
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 |
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 ) );
}
}
|
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.
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
|
....
AddType text/jsp .jsp
AddHandler jserv-servlet .jsp
....
ApJServMount /servlet /root
....
|
<Host name="www.sudsy.net" appBase="/u/website/sudsy">
<Context path="" docBase="/u/website/sudsy"/>
</Host>
|
<taglib>
<taglib-uri>resume</taglib-uri>
<taglib-location>/WEB-INF/tlds/resume.tld</taglib-location>
</taglib>
|
<Host name="www.sudsy.net" appBase="/u/website/sudsy/technology">
<Context path="/technology" docBase="/u/website/sudsy/technology"/>
</Host>
|
ApJServMount /technology/servlet /root |
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 by Phil Selby