How to Dispatch Requests and Information Between Classic Applications and J2EE Applications
Articles and Tips: article
Lead Consulting Engineer
exteNd Product Specialist Group
01 Apr 2003
SilverStream, now part of Novell, was originally known for its RAD Java technology--the page designer, form designer, etc. That technology is being phased out in favor of a J2EE-based application server and frameworks. The last major version of the Novell Application server that will support these "Classic" applications will be 4.x, which means you probably will do all of your new work in J2EE.
Ok great, but now you need to communicate between a J2EE application and a Classic application running on the same server. In the Novell exteNd Application Server 4.0, you can dispatch requests and information between Classic applications and J2EE WAR applications. Of course, this is in addition to redirecting to a URL or to saving information in the database.
While you can communicate between these applications in several ways, this article will focus on dispatching requests via the RequestDispatcher object and on using the Request object to pass information or a token to the dispatchee. All things considered, it is the simplest way to forward control if you need to pass complicated or secure information between a Classic application and a J2EE WAR application. Thus this article is about doing two distinct elements:
Dispatching requests via a forward or include to a different application (classic or J2EE WAR)
Passing objects between these applications
I need to point out that a classic application and a J2EE WAR (Web ARchive file) application are different at more levels than just the APIs they adhere to. All classic applications run within the same context. That is, they have the same session and class loaders between applications.
On the other hand, each J2EE WAR is a separate application by definition and thus each WAR has different session objects and class loader(s). However, the session identification will be the same for a given browser session throughout a particular server, but all other values will be dependent on a particular instance of a WAR or the classic container that is including its attributes.
Note: The above statement is correct for J2EE 1.2 and 1.3 specifications and may change in future releases.
Database and Code Example
Below is an example that contains a SilverStream database and code. Upon installation, you can run the example by opening a browser and going to http://<servername:port>/ClassicJ2EEIntegration/SilverStream/Pages/index.html . From there you can see dispatching and object passing between Classic pages/servlet and J2EE JSPs/Servlets.
Installation instructions are given below:
Install the SilverStream Classic Edition version of the Novell exteNd Application Server 4.0 or greater. To download an evaluation copy, go to http://www.silverstream.com/Website/app/en_US/AppServer , click on "Download" and then click on the "4.0 Classic Edition--NT or Windows 2000" link.
After downloading the application server, download Sybase SQL Anywhere 8 from the same page. Look for the link called "SQLAny80.zip." Follow the instructions under "To use this database, follow the steps below."
Installation instructions can be found at http://www.novell.com/documentation/lg/extendas/sshelp/Docs/helpcore/framesets/instPref.fs.html. Install both the Authenticated Sybase SQL Anywhere 8.0 and the SilverStream Classic Edition.
Install Novell exteNd Workbench 4.1. To download an evaluation copy, go to http://www.silverstream.com/Website/app/en_US/Workbench . Click on "Download" and then click on "exteNd Workbench 4.1--Download Now." Installation instructions can be found at http://www.novell.com/documentation/lg/workbench41/docs/relnotes.html
Unzip the zip file into your C:\ directory. The directory ClassicJ2EEIntegration will be created.
Add a System Environment Variable called AGCLASSPATH with the parameter C:\ClassicJ2EEIntegration\SystemClassPathJars\systemClassPathClasses.jar. Make sure to restart your computer after adding the variable.
Create an ODBC System DSN for the Sybase SQL Anywhere 8 database that is located at C:\ClassicJ2EEIntegration\database\CLASSICJ2EEINTEGRATION.DB and named ClassicJ2EEIntegration with the username of "dba" and with the password "sql."
Add the example database ClassicJ2EEIntegration to your Novell exteNd Application server.
Copy the file C:\ClassicJ2EEIntegration\userLibJars\userLib.jar to <App Server install directory>/userlib.
Dispatching requests allows you to forward control or include content to/from a separate web component without changing the URL. Note that when you forward the request via a RequestDispatcher, it is a server-side forward and the URL will NOT be changed in the browser. Thus, any relative URLs in the HTML that are returned will be relative to the dispatchers URL, not the dispatchee.
For instance, if a Silver Page with a URL of http://<server>/<database>/SilverStream/Pages/firstPage.html forwards the request through a RequestDispather to a J2EE WAR servlet that is listening to http://<server>/<database>/<J2eeWar>/secondPage.html , a relative URL such as \index.html in the resulting HTML page would reference http://<server>/<database>/SilverStream/Pages/index.html and not http://<server>/<database>/J2eeWar>/index.html.
Because the request object is the same between the request dispatcher and the request dispatchee, it allows you to pass objects between the two, which we will talk about in the next section.
Dispatching from a Classic Page/Servlet to a J2EE JSP/Servlet.
Although each WAR defines a standalone application, the Novell server will allow you to forward to a JSP (Java Server Page) or to a servlet in a WAR from a classic page or business object that is running on the same server. In this case, the URL you specify is server-relative. The URL must begin with a slash, followed by the name of the target database. For example, in the pageRequestBegin event for a page, you could add this code to redirect the request to a servlet page:
String servletUrl = "/ClassicJ2EEIntegration/classicJ2ee13War/classicToJ2ee";
ServletConfig sconfig = getServletConfig();
ServletContext sc = sconfig.getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher (servletUrl);
The format for the URL parameter of getRequestDispatcher is /<databasename>/<warurl>/<servlet_or_jsp_url> for a J2EE WAR. In our example, the SilverStream database is ClassicJ2EEIntegration, the WAR URL is classicJ2ee13War and the servlet URL is classicToJ2ee.
To see a running example, go to http://<servername:port>/ClassicJ2EEIntegration/SilverStream/Pages/classicToJ2ee.html . To look at the classic code, open the SilverStream Designer program and look at the page classicToJ2ee in the ClassicJ2EEIntegration database.
Dispatching from a J2EE JSP/Servlet to a Page/Servlet.
The setup and background is very similar between dispatching from a J2EE web component to a classic web component, and vice versa. The difference between dispatching from a J2EE JSP/Servlet is how you look up the ServletContext so that you get a RequestDispatcher that can find the Classic pages and servlets. You must pass a forward slash "/" in getServletConfig. For example, in the doGet method of a J2EE HTTP servlet, you would use the following code to forward a request to a Classic page:
String servletPath = "/ClassicJ2EEIntegration/SilverStream/Pages/pgJ2eeToClassic.html";
ServletContext context = getServletConfig().getServletContext().getContext("/");
RequestDispatcher rd = context.getRequestDispatcher(servletPath);
The format for the URL parameter of getRequestDispatcher is /<databasename>/SilverStream/Pages/<page_name>.html for a Classic page and /<databasename>/<servlet_path> for a Classic servlet. In an example, the SilverStream database is ClassicJ2EEIntegration and the page is pgJ2eeToClassic.html.
To see a running example of this, go to http://<servername:port>/ClassicJ2EEIntegration/classicJ2ee13war/j2eeToClassic . To look at the J2EE servlet code, start the Workbench program and open the project in the classicJ2ee13war.spf file. Then navigate to WEB-INF\classes\com\novell\extend\classicJ2eeIntegration\servlet\ClassicToJ2ee.java. For help on using the Workbench, go to http://www.novell.com/documentation/lg/workbench41/docs/help/framesets/GSwelcome.fs.html
Okay, so far we have learned how to forward or include web components, but let's say you also need to pass information. We can't use the session because, as mentioned above, the J2EE specification requires that the each WAR must have separate sessions. The easiest way to pass information is through the Request object, which is passed along to the dispatchee web component. As you probably recall, you can store information in the Request object through the get/setAttribute() method.
However, you cannot put any object in the Request object and expect the dispatchee to use it exception-free. The reason is that an instantiated object is not a self-contained component within the JVM. The instantiated object must also have the corresponding class definition loaded. Class definitions are found and loaded by ClassLoaders.
ClassLoaders find and load classes. They keep internal lists of the classes they load. A loaded class definition is unique by .class and by the specific instantiated ClassLoader that loaded the class. Thus the same class loaded by different class loaders will create objects that are not identical. All ClassLoaders have a parent except the top-most ClassLoader, which is the System ClassLoader.
When a ClassLoader is asked to find/load a class, the request is first delegated to the ClassLoader's parent to be fulfilled. Thus every request makes its way up the hierarchy of ClassLoaders to the System ClassLoader. If the System ClassLoader cannot find/load the class, then the next class loader down tries to find/load the class and so on down the hierarchy. The class loader hierarchy of the Novell exteNd Application Server 4.0 is depicted in Figure 1.
How Class Loaders work in the Novell exteNd Application Server 4.0.
System ClassLoader --this is the top class loader, which loads classes from the core java.lang package, all SilverStream classes, classes in jars that are referenced in the AGCLASSPATH environment variable, and J2EE classes. There is only one System Class Loader.
Classic ClassLoader --loads all classes running within a classic application that are not found using the System ClassLoader. There is only one Classic ClassLoader. This is a simplistic view (in reality there is also a hierarchy of ClassLoaders), but we will not cover the details in this article. Any JAR deployed on the server is loaded by exactly one classic loader.
J2EE EAR ClassLoader --loads all classes within a specific instance of an EAR (Enterprise ARchive) that are not in a WAR or EJB (Enterprise Java Bean) JAR. A separate EAR ClassLoader is instantiated for each deployed EAR.
J2EE EJB Jar ClassLoader --loads all classes of an EJB JAR and the EJB client JAR. There is one EJB loader for all EJB JARs and EJB client JARs in a specific EAR.
J2EE WAR ClassLoader --loads all classes within a specific instance of a WAR. If a WAR is contained in an EAR and uses an EJB, then the hierarchy of class loaders is System ClassLoader -> J2EE EAR ClassLoader -> J2EE EJB Jar ClassLoader -> J2EE WAR ClassLoader.
A separate WAR ClassLoader is instantiated for each deployed WAR. As denoted by the three arrows leaving the J2EE WAR ClassLoader box in Figure 1, a WAR can be deployed in three general configurations that affect the ClassLoading scheme:
Inside an EAR without EJBs
Inside an EAR with EJBs
So classes loaded by a ClassLoader at a lower level can access any class loaded by a ClassLoader at a higher level (a component can call up the hierarchy, but not down it). For example, any class loaded by the WAR ClassLoader can access any class loaded by the EJB ClassLoader or the EAR ClassLoader. But, the reverse is not true. A class loaded by the EAR ClassLoader cannot access a class loaded by the EJB ClassLoader or WAR ClassLoader.
The next question to answer is which ClassLoader is used to find/load a class. The ClassLoader used is the ClassLoader associated with the current executing object. So to pass a specific object between applications, we need to take into account the specific class loading schemes.
For example, let's say we have a class Foo in a classic application that is jarred up and included in a J2EE WAR application. Then we pass an instance of Foo via the request object from the classic application to the J2EE WAR application. We would get a NoClassDefFoundException error, because different class loaders were used to load Foo: the classic application's class loaders for the classic side and the J2EE WAR class loader--once the request hit the WAR.
There are two options to solve this problem. The first option is to have the .class files in a JAR in the System Classpath, since it is the ultimate parent ClassLoader for all ClassLoaders. The second option is to serialize/deserialize our object in some manner.
There are two broad methods to serialize/deserialize your object. The first is to use Java Serialization to manually serialize/deserialize the object. The second is to use some type of remote procedure call that automatically serializes/deserialzes your objects. For example, a Remote EJB call, CORBA, and JRMP. We are going to focus on EJB.
We are going to show you five different ways to pass objects using:
An EJB (Enterprise Java Bean)
The simplest way to get around class loading problems is to put all classes that might be loaded by the different class loaders in a JAR that is loaded by the System ClassLoader. To do this, you would need to JAR up your classes and put them either in the System Classpath or in the <AppServer Install directory>/userlib directory.
On Windows you would create a System Environment variable called AGCLASSPATH, reference the JAR file, and restart your box. Note that all SilverStream, J2EE and java.lang classes are automatically loaded by the System ClassLoader.
Here are two different examples of passing objects using the System Classpath. The first example is utilizing only classes that are normally loaded by the System ClassLoader, such as the String class. As noted above, you do not have to do anything special to get this to work (such as setting anything in the System Classpath). To see a running example of this, go to http://<servername:port>/ClassicJ2EEIntegration/ SilverStream/Pages/classicToJ2ee.html, enter some text and hit the "String" button.
To look at the classic code, open up the SilverStream Designer and go to database ClassicJ2EEIntegration -> page classicToJ2ee -> method handle_btnString_pageActionPerformed. To look at the J2EE servlet code, start the Workbench program and open up the project classicJ2ee13war.spf file. Navigate to WEB-INF\classes\com\novell\extend\classicJ2eeIntegration\servlet\ClassicToJ2ee.java. Look for the comment //Passed via a String object in the request object.
The second example of passing objects using the System Classpath utilizes a custom class (i.e., one you have written) that is jarred up and referenced in the System Classpath. To see a running example of this, go to http://<servername:port>/ClassicJ2EE Integration/SilverStream/Pages/classicToJ2ee.html , enter some text and hit the "Custom Class on System Class Path" button.
To look at the classic code, open up the SilverStream Designer program and go to database ClassicJ2EEIntegration -> page classicToJ2ee -> method handle_btnSystemClassPath_pageAction Performed.
To look at the J2EE servlet code, start the Workbench and open up the project classicJ2ee13war.spf file. Then navigate to WEB-INF\classes\com\novell\ extend\classicJ2eeIntegration\servlet\ClassicToJ2ee.java. Look for the comment //Passed through a custom object in System classpath.
JARs that are located in the <App Server install directory>\userlib directory are made available to J2EE applications if their deployment plan references the JARs. This allows mulitple J2EE applications to reference a JAR without having to use the System classpath or to add the JAR to every application. You would add the following code to the deployment plan for the J2EE archive:
<classpathJars> ... <userlibJars> <el>userLib.jar</el> </userlibJars> </classpathJars>
To see a running example of this method, go to http://<servername:port>/ClassicJ2EEIntegration/ SilverStream/Pages/classicToJ2ee.html, enter some text and hit the "Custom Class in userlib directory" button. To look at the classic code, open up the SilverStream Designer program and go to database ClassicJ2EEIntegration -> page classicToJ2ee -> method handle_btnUserLib_pageActionPerformed.
To look at the J2EE servlet code, start the Workbench and open up the project classicJ2ee13war.spf file. Then navigate to WEB-INF\classes\com\novell\ extend\classicJ2eeIntegration\servlet\ClassicToJ2ee.java. Look for the comment //Passed via a custom object in /userlib directory.
I have provided an example of manual serialization for completeness, but normally you would not pick this technique.The key is that any class that is serialized must implement the Serializable interface. For more information on Serialization, see the Sun Java tutorial at http://java.sun.com/docs/books/tutorial/essential/io/serialization.html
To see a running example of this method, go to http://<servername:port>/ClassicJ2EEIntegration/ SilverStream/Pages/classicToJ2ee.html, enter some text and hit the "Serialization" button. To look at the classic code, open up the SilverStream Designer and go to database ClassicJ2EEIntegration -> page classicToJ2ee -> method handle_btnSerialization_ pageActionPerformed.
To look at the J2EE servlet code, start the Workbench and open up the project classicJ2ee13war.spf file. Then navigate to WEB-INF\classes\com\novell\extend\ classicJ2eeIntegration\servlet\ClassicToJ2ee.java. Look for the comment //Passed via serialization.
If you need to pass objects that you change frequently, it would probably be easier to use an EJB to pass your objects because the EJB will automatically handle the serialization process for you. The basic steps are as follows:
Store the object(s) to be passed with a stateless session bean using a unique token as the key.
When the request is forwarded, pass the token from the dispatcher to the dispatchee.
The dispatchee calls the stateless session bean passing the token.
The stateless session bean looks up the object(s) via the token and passes them back to the dispatchee.
The example demonstrating this technique goes one step further and passes all serializable session attributes to the dispatchee through a hash table. The specific steps are as follows:
Start the page classicToJ2ee, enter text in the textbox, and press the "EJB" button.
This executes the handle_btnEJB_pageActionPerformed and puts text into the Session via setAttribute. All of the Session attributes will be moved to a hash table through the static method HashSession.putSessionAttributesInHashtable(). The hashtable and session ID is then passed to the stateless session bean SBClassicSessionInformation.
SBClassicSessionInformation stores the hash table that is keyed through the session identification. Storage is achieved by using a static member variable SessionHash. SessionHash has a static hash table of session hash tables that is keyed through the session ID.
The handle_btnEJB_pageActionPerformed then puts the session ID into the request object for the dispatchee to retrieve.
The handle_btnEJB_pageActionPerformed calls the forwardToServlet, which will forward the request to the target URL.
The servlet ClassicToJ2EE handles the forward. It determines which specific URL was used to invoke the servlet; in this case, the EJB. The getSession() method is calling the SBClassicSessionInformation bean, passing the session ID it retrieved from the request object.
The SBClassicSessionInformation bean will return the hash table of session attributes by looking it up with the session ID that is passed to it.
To see a running example, go to http://<servername: port>/ClassicJ2EEIntegration/SilverStream/Pages/ classicToJ2ee.html, enter some text and hit the "EJB" button. To look at the classic code, open up the SilverStream Designer and go to database ClassicJ2EEIntegration -> page classicToJ2ee -> method handle_btnEJB_pageActionPerformed.
To look at the J2EE servlet code, start the Workbench and open up the project classicJ2ee13war.spf file. Then navigate to WEB-INF\classes\com\novell\extend\classicJ2eeIntegration\servlet\ClassicToJ2ee.java. Look for the comment //Passed via EJB.
Structure your application to pass control between environments and avoid passing data when possible. If you need to pass information and are not passing custom classes between containers, just use the Request object.
If you have to pass custom classes that are fairly static (you don't change the code--ever) or that are used by more than one J2EE application, I would consider jarring up the classes and putting them in either in the system class path or in the userlib directory. If you have to pass custom classes that do or might change, I would have you consider using the EJB technique.
* Originally published in Novell AppNotes
The origin of this information may be internal or external to Novell. While Novell makes all reasonable efforts to verify this information, Novell does not make explicit or implied claims to its validity.