[an error occurred while processing this directive]
> developer > web app development
J2EE Packaging and Class Path Dependencies
by Martin Holzner, Lead Consulting Eng. , Novell
Date Created: 2002-02-08 14:45:00.000
  The Web Tier
  The Business Tier
  Bringing the Two Together
  Packaging the Two as an EAR
  Notes and Comments
  Resources

the web tier
Let's start with the servlet. (It will be part of a WAR file.) In my last article I showed you how to write a servlet, package it into a WAR, and finally deploy it to a J2EE compliant application server. All pretty easy, right? No way, you say? You're right. J2EE is complex, involving lots of little steps that depend on each other. J2EE isn't easy, but there are some cool new tools that have come to the rescue, like the following just to name a few:

  • Ant - An open source build tool by Apache that allows you to create repeatable builds of your software
  • XWB - exteNd Workbench, a free tool that allows you to develop, assemble, and deploy J2EE archives
Of course, you can also use your favorite IDE and do everything by hand, or use some other thinkable combination. Now, to recap something I covered in my last article, we'll create a servlet in a WAR and deploy it -- only this time we'll use XWB.

Here's how:

  • Get XWB, install, and open it
  • Create a new WAR

    File -> New Project -> WAR

    call the project "WAR1" and place it in "c:\work\war1"

  • Switch to the archive layout (from source layout) on top of the navigation tree
  • Create a new servlet and add it to the just created WAR

    File -> New -> Servlet

    call the servlet class "Servlet1" and the package "war1";

    generate a doGet and an init method; for the rest we accept the defaults

  • Open the deployment descriptor (web.xml) for the WAR and add a servlet mapping for the newly created servlet right click on "WAR1.spf" in the left navigation tree and choose "Open Deployment Descriptor"

    in the descriptor right click on "Servlet Mapping" and choose "Add"

    right click on "Untitled" and choose "Properties"

    fill in "Servlet1" (Servlet name) and "/servlet1" (URL pattern)

  • Save and build the project File -> Save

    Projet -> Build and Archive

  • Open the deployment plan for the WAR

    right click on "WAR1.spf" in the left navigation tree and choose "Open Deployment Plan"

    choose "Yes" to create a plan, and pick the server type for deployment

  • Save the deployment plan and deploy the WAR

    File -> Save

    Project -> Deploy Archive

    (If you haven't created a server profile yet, you'll have to do that first: Edit -gt; Profiles -> Server tab -> new)

Congratulations: You've just created a working servlet that you can call via a browser without writing a single line of code, editing a bunch of batch files, or entering some crazy jar commands on the command line.

the business tier
OK, that was easy, wasn't it? But what about the second part, the EJB? Let's do that as well using XWB. Since this is not an article about EJBs, I'll keep this very short so we can finally get to the real topic, the class path.

If you are not experienced in EJB, you should still be able to follow the steps below to create a SessionBean.

Here's how to create an EJB jar project containing a stateless SessionBean.

  • Create a new EJB jar File -> New Project -> EJB

    let's call the project "EJB1" and place it to "c:\work\ejb1"

    choose to create an EJB client jar, call it "EJB1-client"

  • Create a new stateless SessionBean

    File -> New -> EJB

    choose "Session EJB", stateless

    create separate EJB-client & EJB JARs

    choose "ejb1" as the package name for the EJB1 and the EJB-client projects

    create the EJB from scratch

    choose "Ejb1" as the base name

    add a method "callMe" that returns string and takes a string param

    accept the defaults for the rest of the wizard

    modify "ejb1.SBEjb1Bean.callMe()" to return the string that was passed in

Since we want to keep the EJB part simple, we won't put any further function in this layer. Now we can build the ejb jar.

Note that building the EJB1 project will automatically build the EJB1-client project as well. And, as we'll discover later (see "Adding the Manifest"), XWB views other things for us as well.

Now, all that's left to do is deploy the bean. To do so, we need to give the bean a JNDI name under which it can later be discovered, so we have to create a deployment plan for the ejb jar. This can be done as follows:

  • Open the deployment plan for the EJB jar

    right click on "EJB1.spf" in the left navigation tree and choose "Open Deployment Plan"

    choose "Yes" to create a plan, and pick your server type for deployment

  • Map the SessionBean to a JNDI name

    right click on "SBEjb1" in the deployment plan, and choose "Properties"

    choose "ejb/ejb1" as the JNDI name

  • Save the deployment plan and deploy the EJB jar

    File -> Save

    Project -> Deploy Archive

Congratulations again: You've just created and deployed a stateless SessionBean that you can now call from other resources.

OK, finally, it gets a bit more exciting!

bringing the two together
Calling the SessionBean from the Servlet

By now you've most likely seen this a couple of times, so I'll just show you the code that you'll need to add to your servlet so you can use the EJB.

Make modifications to the servlet's init method:

public void init( ServletConfig config ) throws ServletException
{
    super.init( config );

    String msg = null;

    try
    {
       Context ctx = new InitialContext();

       Object obj = ctx.lookup("java:comp/env/beanrefs/ejb1");

       home = (SBEjb1Home)PortableRemoteObject.narrow(obj,SBEjb1Home.class);
    }

    catch (Exception e)
    {
       e.printStackTrace();
    }
}

Make modifications to the servlet's doGet method:

public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException
{
    response.setContentType( CONTENT_TYPE );

    PrintWriter out = response.getWriter();

    String msg = null;

    try
    {
       SBEjb1 ejb1 = home.create();

       msg = ejb1.callMe("give me my message back!");

       ejb1.remove();
    }

    catch (Exception e)
    {
       msg = e.getMessage();

       e.printStackTrace();
    }

    out.println( "<html><head><title>Servlet1</title></head><body>" );

    out.println("<P>the bean wants to tell us this: " + msg + "</P>");

    out.println( "</body></html>" );
}

Add this instance variable:

private SBEjb1Home home = null;

Add the following import statements:

import ejb1.*;

import javax.naming.*;

import javax.rmi.*;

Getting the Compile Time Class Path Right

You guessed it. This is cool, but it doesn't compile! We need to make the used ejb classes available in the project's class path. What bean classes are we using? The home and remote interface classes, nothing else. So all we need is the EJB1-client.jar. Add this to WAR1's class path as follows:

Project -> Project Settings

Classpath tab

Add Archive... and point to the "EJB1-client.spf" file

Note that EJB1-client.spf is not a typo. When you add the spf (the project definition) to the class path, XWB checks the project for changes and builds them if necessary before building the WAR project. And, if you modify the class path entry, e.g., "..\ejb1\EJB1-client.spf", you'll make your project independent from fixed directory structures.

Now, try to build and deploy the WAR. If you are lucky like I was, it will work. Cool, we're done. Or are we? You guessed it. We're not! Try to call the servlet. When you point your browser to "http://server/db/WAR1/servlet1", you'll get something like "an unexpected server exception occurred while processing...".

So what's wrong?

Actually, a couple of things:

  • We called a bean reference in our servlet to lookup the bean in the servlet's environment, but we never mapped that bean reference.
  • We told the WAR project where to get the bean classes at compile time, but didn't for runtime.

Now let's fix it.

Getting the Runtime Class Path Right

Here's where the spec writers remembered how useful applets are in loading classes from outside the jar in which they're packaged. The mechanism is called virtual class path, or manifest class path. How does it work? The servlet container introspects the manifest file of the WAR and checks for class path entries. In case there are archives defined in that file, the container extends the runtime class path of the WAR by those mentioned archives. Currently, the various J2EE servers differ quite a bit. To be cross-vendor compatible, the safest approach is probably to package the WAR and EJB jar in an Enterprise Archive (EAR). I'll show you how this can be accomplished later. (See "Packaging the Two as an EAR".) But for now we'll deploy the two archives separately. In this case, we'll need to make sure that resources in the WAR can access the EJB resources at runtime. To accomplish this, we'll extend the WAR's manifest class path to include the EJB's remote jar. This jar is created by the containers deployment tool when you deploy the ejb jar. It's needed to communicate with the enterprise bean, since it contains the RMI stubs that talk to the bean remotely. But check your particular server (and version) to make sure this works.

At this point, let's assume we'll deploy to a SilverStream Application Server (>= 3.7.4), and we'll use XWB 1.1 or SilverCmd to deploy the archives to SilverStream. Note that if you don't specify a name for the remote jar, the name will be generated for you. The naming pattern for this is jar name + "Remote.jar" (e.g., EJB1.jar becomes EJB1Remote.jar). You can check SilverStream's Objectstore for the names of all deployed objects (http://server/db/SilverStream/Objectstore/Jars or Jars in the SilverStream Designer).

Adding the Manifest

Currently, XWB 1.1 does not support creating and adding the manifest. Adding that capability to XWB is on SilverStream's to do list, but for now the fastest way I know of to create and add the manifest is to go to your Explorer and create a text file called "MANIFEST.MF" in "c:\work\war1\META-INF", then enter the following two lines:

Manifest-Version: 1.0

Class-Path: EJB1Remote.jar

Note that it's a good thing to make sure you have a line break after the class path line as some containers read the content of this file via BufferedReader.readln().

Now we can go back to XWB and add the file to the WAR project.

Right click on WAR.spf and choose "Add File to Project..."

Point to META-INF/MANIFEST.MF

Add the file to the WAR1 project at this location: "META-INF/MANIFEST.MF"

And that's it. We've just extended the virtual class path. Now we only have to add the bean reference and we'll be done.

Map the SessionBean as a Bean Reference

  • Open the WAR's deployment descriptor and scroll all the way down to the EJB references
  • Right click on EJB references and choose "Add"
  • Right click on "UntitledBeanReference" and choose "Properties"
  • Name the bean reference "beanrefs/ejb1"
  • Leave the referenced bean name empty
  • Enter "ejb1.SBEjb1Home" for the home interface
  • Enter "ejb1.SBEjb1" for the remote interface

You can make the link here by filling in the referenced bean name, or you can do it later in the deployment plan. I prefer to create the reference here and link the bean later to the JNDI name I've selected for it. (I've had trouble in the past when entering the bean's name here. In most cases the lookup couldn't find the bean.)

Now to deploy our modified WAR.

  • Open the deployment plan. (In case you left the referenced bean name empty in the deployment descriptor, you'll see a "beanrefs/ejb1" EJB reference. Just right click on it and choose "Properties". Fill in the JNDI name of your SessionBean "ejb/ejb1".)
  • Save and deploy.
Yes, it still deploys successfully. No change there. But now, do the ultimate test: Call the servlet.
packaging the two as an EAR
This is another deployment option. Without any code changes, you can repackage the two archives to be deployed in one single step. Doing so will create a new EAR project and add EJB1, EJB1-client, and WAR1 as subprojects to the root of the EAR. XWB will make sure the deployment descriptor (META-INF/application.xml) for the EAR is updated for each added subproject. To deploy the two projects as part of an EAR, you'll need to create a deployment plan for the EAR, just as you did for the two separate archives. But before you create the plan, go back to the WAR's deployment descriptor (web.xml) and fill in the referenced bean name "SBEjb1". You can try different ways here, but this is the only way that worked for me. (Compare this to leaving it blank and mapping it in the deployment plan as we had to do when deploying the two archives separately.)

Now we can create the deployment plan and fill in the bean's JNDI name, save the plan, and deploy the EAR.

Since the two archives are being deployed in the same process, and the EJB's remote jar is generated as part of that deployment step, you can't reference the remote jar in the manifest class path as we did in the case of the two separate archives. (Well, you can, but it will be ignored.) The EAR deployment will take care of that for you. This is a SilverStream feature, and it might not work on every application server. Some other vendors might ask you to add a manifest class path entry for the ejb jar in some form.

notes and comments
  • I've sometimes experienced problems with the compile time class path in the form that javax.ejb.EJBObject or javax.servlet.Servlet could not be found. When this occurred, I had to manually add ejb.jar and servlet.jar to the projects class path. (I used the jars in &Silverstream_Home%\lib.)
  • If you need to add more then one jar to the manifest classpath, you can do so by separating the names with a space (e.g., Class-Path: Jar1.jar Jar2.jar Jar3.jar).
  • I cache the EJB's home interface in the servlets init() method to save lookups, which produce heavy weight ORB classes.
  • XWB generates a SPF file for every project. When the project is created, XWB fills in absolute path information. If you want your project to be independent of drive mappings and folders, you'll need to open the SPF file with a text editor and modify the entries to relative paths.
resources
arrow  ClassLoading
arrow  Extention ClassLoading
arrow  J2EE Home page
arrow  J2EE Technology Center
arrow  Professional Java Server Programming J2EE 1.3 (Wrox)