|
|
DOMINO INTEGRATION
Add Collaborative Features to Web Apps with Domino Objects
Plan this right from the beginning and you can smoothly add messaging, directory, and workflow functionality to your WebSphere applications.
IBM WebSphere Application Server and Lotus Domino, when used together, provide unmatched functionality for developing and deploying Web applications. WebSphere provides the Java component framework and high-volume scalability features, while Domino supplies rich application and collaboration services serious commercial Web applications require, such as messaging, directory, background agents, and workflow.
One obvious way to hook the two together is to build Domino services into your WebSphere-based servlets, Java Server Pages (JSP), and Enterprise JavaBeans (EJB) by simply using the Domino Java object library. Domino's Java objects offer easy access to rich application functionality within a WebSphere application framework. You just put the Domino .JAR file on your CLASSPATH, code an import statement, and create a Session object instance, then you're off and running.
Or are you?
Using the Domino objects from WebSphere components actually requires a bit of forethought and planning. Depending on the needs of your application, you might code Domino access very differently. And if you don't pay attention to the details and the tradeoffs, you may discover late in the game that your application doesn't scale.
The major up-front considerations are:
- Should Domino and WebSphere be located on the same machine?
- Which version of the Domino objects should you use, local or remote?
This article discusses the tradeoffs involved with both of these decisions, and goes on to present recommended design patterns for using Domino objects both locally and remotely from WebSphere Application Server. I'll show you some sample code that demonstrates the two basic patterns, and examine the performance differences.
Architectural considerations: local or remote access?
One of the nice features of WebSphere Application Server for large enterprise applications is that it lets you break your application into separate pieces, each of which can be distributed to a separate computer. You might, for example, have your servlets and JSPs on one machine, and your EJBs on another. You can have a servlet or EJB that uses Domino objects, but those objects are accessed remotely and live on another computer.
The essential tradeoff here is CPU load vs. network load. At one extreme, you have all the code in your application running on a single CPU, with no network overhead at all, except between the browser client and the server. At the other end of the scale, you have the application broken into multiple, cooperating execution components, each running on a different computer, communicating over a network using one or more protocols.
The vast majority of performance-critical, enterprise Web applications falls between these two extremes. Typically, small groups of components are co-located, and big pieces, such as the back-end database, the Domino server, or the Enterprise JavaBeans, are remoted to other server machines to gain CPU power at the cost of some network bandwidth.
WebSphere developers are fortunate because Domino supports both local and remote access using virtually identical object models. Both options are available through the identical set of Java interfaces contained in the lotus.domino package. The only difference is the .JAR file you put in your CLASSPATH: notes.jar for local access, or ncso.jar or ncsow.jar for remote access (more on the difference between the two remote access .JAR files in a moment).
At the conceptual level at which you deal with Domino constructs (sessions, databases, documents, views, and so on), the coding is the same whether your objects are on the same machine as your Java code (servlet, JSP, or EJB), or on some other machine on the network. This is a big win in terms of development simplicity, but there's a fly in the ointment of smooth Domino programmability: thread management.
When using the "local" Domino classes from any Java program, you're essentially calling through a thin layer of Java code into the Domino "back-end" code, which is implemented in C++. The Java "wrapper" classes use a standard Java-to-C calling mechanism, known as Java Native Interface (JNI), to access the "real" Domino classes in the product's .DLLs (or whatever the platform-specific equivalent of a .DLL is). The Domino code is loaded "in process" in the Java Virtual Machine (JVM), which is great from the point of view of performance: You're getting the best possible speed out of the hook-up between the Java and C code -- everything is right there in the computer's memory.
But the Domino back-end requires initialization. That's not a problem in and of itself -- you can certainly do process-wide initialization of Domino when the Java library is first loaded. This shouldn't be a big issue because you only pay the performance penalty once: the first time a WebSphere component accesses the Domino .JAR file.
However, Domino also requires per-thread intialization and termination. The reasons for this aren't explicable within the scope of this article, but the implication is that in Java you must initialize every thread of execution on which you want to use Domino objects. You must likewise terminate every such thread for Domino usage when you're done. What happens if you don't? (Developers always ask me this.) If you don't initialize the thread before using Domino objects, your program will either fail with an exception, or crash. If you don't terminate each thread that uses Domino objects before the thread terminates, you risk having your process (the WebSphere server, for example) hang or crash the next time it's shut down. It's pretty ugly in either event.
The local Domino object library provides a useful class, lotus.domino.NotesThread, to help you automatically take care of this required initialization and termination. If you use NotesThread instead of java.lang.Thread to spawn a thread of execution, the required Domino init/term calls occur automatically. NotesThread extends Thread, so there's no loss of functionality.
What happens if you don't control the threads on which your program executes? What if your program is a WebSphere servlet or Enterprise JavaBean? Such programs run in a multi-threaded fashion, but they don't themselves have the opportunity to create the threads on which they run. The server (i.e., servlet engine, EJB container) does that for them. The Domino NotesThread class provides two static methods for just this eventuality: NotesThread.sinitThread() and NotesThread.stermThread(). You can invoke these calls to initialize or terminate Domino activity on the current thread of execution. This lets you use Domino objects on threads you don't control yourself. Just call the initThread() method before using any Domino objects in a servlet or EJB, and then call termThread() when you're done, and everything should be fine.
Or will it?
Actually, the fact that you have to do this init/term sequence on all threads turns out to affect the entire coding pattern for using Domino objects. The reason for this is that you can't cache any Domino objects across the init/term boundary. As a result, you have to re-acquire a Session and all other object instances each time your servlet or EJB entry point is called. This requirement (which I hope IBM will fix in a future release of WebSphere Application Server) might severely limit the scalability of your application.
By contrast, if you use the remote object library for Domino (the CORBA classes), you aren't accessing any Domino C/C++ code from within the JVM's process space, so there aren't any special requirements for thread initialization or termination. You can instantiate a Session object (or any object in the Domino hierarchy) and keep it around for re-use later. This is a big win, although of course you take a performance hit by having all calls remoted across a network. Even if you're using the CORBA classes to communicate with a Domino server on the same machine as your WebSphere program, you still pay for having all method invocations processed remotely (marshalling the arguments, formatting data according to the IIOP wire protocol specification, transmitting the call, de-marshalling the parameters, finding the remote object to invoke, and so on). The bits may not actually go out over the network, but they have to travel through your network adapter card and be processed on both ends of the conversation as if there were a network cable in between.
The next two sections provide more detailed examples of how you should code Domino access in the local and remote scenarios. I've taken a simple bit of access logic (retrieving a document from a view by its key, and then retrieving a column value from the document's summary buffer) and implemented it appropriately for both the local and remote cases. You'll see how different the two techniques are, even though the calls made to the Domino objects are identical.
You can download the sample database and source code from both WebSphereAdvisor.Com (http://www.WebSphereAdvisor.com/Article/ balab03) and the Looseleaf Web site (http://www.looseleaf.net). I purposely kept the sample application as simple as possible, the point being to illustrate how you should use the Domino objects, not to impress you with a slick Web application.
Figure 1 is a screen shot of the main view in the sample database. The first column, labeled "Key," is the unique key value for each document. The second column, called "Computed," contains computed values: a concatenation of three individual fields in the document.
Figure 1: Local/remote database contents -- You can see the key value for each document, as well as the concatenation of the three individual fields in each document.
Local Domino access pattern
The following code listing contains all the source code for a simple WebSphere servlet that accesses some Domino data. You build and run this version of the servlet using Domino's notes.jar library. The initial code framework was generated by IBM's VisualAge/Java 3.5.
/* Copyright 2000, Looseleaf Software, Inc.
All rights reserved.
*/
package com.looseleaf.localDomino;
import lotus.domino.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class LocalDomino extends javax.servlet.http.HttpServlet
{
/**
* Process incoming HTTP GET requests
*/
public void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException
{
performTask(request, response);
}
/**
* Initializes the servlet.
*/
public void init()
{
// in the local case there's nothing we can
// initialize for the servlet
}
/**
* Process incoming requests for information
*/
public void performTask(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
{
Session s = null;
java.io.PrintWriter writer = null;
try {
response.setHeader("Pragma", "No-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Cache-control", "no-cache");
// prepare output stream
response.setContentType("text/html");
writer = response.getWriter();
// init the current thread
NotesThread.sinitThread();
// acquire a session instance,
// use a name/password that
// exists in the local Domino directory
// specify null for host to force local access
s = NotesFactory.createSession((String)null,
"Servlet User/Looseleaf", "password");
if (s == null)
{
writeResult(writer,
"Unable to create Domino Session");
return;
}
// get the database and the view
Database db = s.getDatabase("",
"advisor\\Local-Remote.nsf");
View view = db.getView("All");
// look up a document, get the value in the second
// column of the summary buffer and write it out
// The key comes from an input parameter
String key = request.getParameter("key");
if (key == null)
{
writeResult(writer,
"No valid key parameter found; making one up");
key = "1";
}
Document doc = view.getDocumentByKey(key);
if (doc == null)
{
writeResult(writer,
"Key does not match any document");
return;
}
// get the value
java.util.Vector values = doc.getColumnValues();
String val = (String)values.elementAt(1);
String output = "Document " + key +
" had computed value: " + val;
writeResult(writer, output);
}
catch(Exception e)
{ e.printStackTrace(); }
finally {
// recycle the Session and terminate the thread
try { if (s != null) s.recycle(); }
catch (Exception x) { x.printStackTrace(); }
NotesThread.stermThread();
}
} // end performTask
private void writeResult(java.io.PrintWriter writer, String text)
{
// output some html stuff
writer.write("<html><body>");
writer.write(text);
writer.write("</body></html>");
}
} // end class
Note, first of all, how short this servlet is. Most of the heavy lifting is done in the Domino object code. Second, note that there isn't any code in the servlet's init() method. Normally this is the place where you do one-time intialization of servlet resources, such as database connections. But because you can't cache any Domino resources across thread initialization/termination boundaries, you have nothing to do in the init() method.
A browser GET invocation of this servlet causes the servlet engine to call the doGet() method, which in turn simply calls performTask, passing through the standard request and response objects. This framework isn't required; it's just how the skeleton code was generated by the VisualAge for Java wizard.
The performTask() method initializes the current thread for Domino and gets an authenticated lotus.Domino.Session instance using a hardwired username and password. From there, you just get a database and a view, and again the names are hardcoded into the servlet.
Note that before getting the output stream from the "response" object, I tell the Web server not to cache the result page. Handling it this way makes for a more valid test when you benchmark the two patterns.
The servlet expects an input parameter on the URL named "key." This lets the browser client specify which document to retrieve from the database. The format of a URL specifying a key value would be something like this:
http:// www.myhost.com/servlet/LocalDomino?&key=1
If you don't find a key value on the URL, you can just pick one. The key value corresponds to the first sorted column in the view in which you're going to look. The call view.getDocumentByKey() retrieves a document instance for the document matching the key, if there is one. Once you have the document, you can retrieve all the values displayed for that document in the view. The getColumnValues() call returns a Vector containing a value object for each column in the view. You want the second column, named Computed, in the view. So, you just pull out element number 1 from the Vector and write it back to the browser.
Because you've initialized the thread for Domino, you must also be 100 percent sure to terminate it; otherwise, Domino eventually hangs or crashes the servlet engine. Putting the NotesThread.stermThread() call in a Finally block ensures that the terminate call is invoked, regardless of any other exceptions that might happen in the code. Before you terminate the thread though, you also need to tell Domino to free up the memory in its C memory heap that corresponds to the Java objects. The JVM will, of course, garbage collect the Java objects, but there's no notification mechanism between the JVM and Domino that tells Domino the objects went away. So, I use the recycle() call on the Session object to force memory reclamation on the Domino side. Recycling the Session implicitly recycles all the Session object's child objects, including the database, the view, and the document, so only the one call is necessary.
This servlet works just fine, but notice how each time a Get call is made on it, you have to repeat a lot of work: Instantiate a Session, authenticate the user name and password, open the database, find the view, and look up the document. All but the last step will be identical every time. This extra work is bound to affect the overall scalability of this servlet in a high-volume Web situation.
The next section shows how you can recognize this pattern when using the remote CORBA classes for Domino.
Remote Domino access pattern
The following is the source code for the remote version of the same servlet. You would build and run this version of the servlet using the remote Domino library, ncsow.jar. This servlet framework was also originally generated by VisualAge for Java 3.5.
/* Copyright 2000, Looseleaf Software, Inc.
All rights reserved.
*/
package com.looseleaf.remoteDomino;
import lotus.domino.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class RemoteDomino extends javax.servlet.http.HttpServlet
{
// member variables
private lotus.domino.Session session = null;
private lotus.domino.Database database = null;
private lotus.domino.View view = null;
/**
* Process incoming HTTP GET requests
*/
public void doGet(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException, java.io.IOException
{
performTask(request, response);
}
public String getServletInfo()
{ return super.getServletInfo(); }
/**
* Initializes the servlet.
*/
public void init()
{
// we can get the session, database and view
// objects just one time and cache them
try {
this.session = NotesFactory.createSession("127.0.0.1",
"Servlet User/Looseleaf",
"password");
this.database = this.session.getDatabase("",
"advisor\\Local-Remote.nsf");
this.view = this.database.getView("All");
}
catch (Exception e) { e.printStackTrace(); }
}
/**
* Called when the servlet is destroyed
*/
public void destroy()
{
// clean up the cached objects
try {
if (this.session != null)
this.session.recycle();
}
catch (Exception e) { e.printStackTrace(); }
}
/**
* Process incoming requests for information
*
* @param request Object that encapsulates the request to
* the servlet
* @param response Object that encapsulates the response
* from the servlet
*/
public void performTask(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
{
java.io.PrintWriter writer = null;
lotus.domino.Document doc = null;
try {
response.setHeader("Pragma", "No-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Cache-control", "no-cache");
// prepare output stream
response.setContentType("text/html");
writer = response.getWriter();
// look up a document, get the value in the second
// column of the summary buffer and write it out
// The key comes from an input parameter
String key = request.getParameter("key");
if (key == null)
{
writeResult(writer,
"No valid key parameter found, making one up");
key = "1";
}
doc = this.view.getDocumentByKey(key);
if (doc == null)
{
writeResult(writer,
"Key doesn't match any document");
return;
}
// get the value
java.util.Vector values = doc.getColumnValues();
String val = (String)values.elementAt(1);
String output = "Document " + key +
" had computed value: " + val;
writeResult(writer, output);
}
catch(Exception e)
{ e.printStackTrace(); }
finally {
try { if (doc != null) doc.recycle(); }
catch (Exception x) { x.printStackTrace(); }
}
}
private void writeResult(java.io.PrintWriter writer, String text)
{
// output some html stuff
writer.write("<html><body>");
writer.write(text);
writer.write("</body></html>");
}
} // end class
The big win in using the remote classes is that you can ignore the whole thread init/term issue, and move the code involved in navigating to the Domino View object, which you're going to use over and over, into the init() method, which is only called once. The corresponding cleanup code goes into the destroy() method, which the servlet calls before destroying the servlet instance. The NotesFactory.createSession() call looks just about the same as before, except you're specifying the DNS name or IP address of the remote server from which you want to acquire a Session. Note that you should specify the IP address or DNS name of the computer, as that machine's Domino DIIOP task expects it (100.100.100.6 in my case).
The local runtime code in ncsow.jar will use IIOP (CORBA's wire protocol) to communicate to the specified server, authenticate the provided username and password, and create a Session instance for the servlet's use. The Session instance hangs around until you recycle it, or until the connection to Domino times out. All Domino objects created using this Session likewise resides on the remote Domino machine. Any method invocations you make are sent over the network to the server and executed there. What look like local Java object instances are really just instances of proxy objects, whose only job is to communicate with the "real" objects over on the Domino server. This is a standard CORBA setup.
The performTask() method in this version is simple: Look up the document you want using exactly the same code as in the LocalDomino servlet, get the column values Vector, and so on. The only real difference between the two versions of the method is, in the remote case, you call recycle() on each Document as you finish with it. If you don't do it this way, Document object instances accumulate on the Domino server until the servlet is destroyed (usually only when the WebSphere server shuts down for some reason), eventually causing the Domino server to run out of memory.
Benchmarking the two patterns
So, which technique is preferable? There are performance tradeoffs inherent with both approaches: One has much faster access to Domino calls, but does things more repetitively, while the other is a "cleaner" architecture requiring fewer actual calls, but perhaps operating more slowly because each call has to be remoted over the network. In low- or medium-volume user situations, it probably doesn't really matter which way you go. But it really isn't obvious which way is going to be better in high-volume situations, where scalability will matter a great deal.
How do you figure it out? The only reliable way is to test and measure. I wrote a Java program to invoke each servlet 100 times in a loop and time the results. The results were that 100 iterations using the local pattern took 40 seconds, while 100 iterations using the remote pattern (on the same machine) took only 24 seconds. That's a 40 percent time reduction. Of course, if the Domino machine were truly remote from the WebSphere machine, the gain wouldn't be quite as good.
Positioned for future changes
The design patterns I've illustrated here for both local and remote access to Domino objects and services use exactly the same calls, but are structured very differently. Which way you go for your application will, of course, depend on your specific needs. Even if you're developing your servlets and/or EJBs in an environment where both WebSphere Application Server and Domino are on the same machine, it may make sense to use the CORBA classes anyway, since you may decide at a later time that in production the two servers shouldn't be co-located.
If and when IBM enhances WebSphere Application Server so that the whole thread management mess becomes unnecessary for local access to Domino, the local design pattern will (thankfully) become obsolete, and your code (with the trivial exception of the NotesFactory.createSession() call) can be the same regardless of where the Domino objects live. That will be a good thing.
Bob Balaban joined Lotus Development Corp. in 1987, where he worked as a software engineer on several versions of 1-2-3. In 1993, Bob began working at Iris Associates on what eventually became Notes 4. Bob authored the "back-end classes" for LotusScript (and then, in Notes 4.6, for Java as well). From 1997 to 2005 Bob was president (and janitor) of Looseleaf Software, Inc., a small consulting, development, and training company focused on Notes and other IBM products. Bob re-joined IBM in 2005, where he is now serving as Programming Services Architect for Notes and Domino. In his current role, Bob oversees issues of programmability and application development for the product. http://www.bobzblog.com
ARTICLE INFO
FREE ACCESS
Keyword Tags: Collaboration, Enterprise Java Beans (EJB), IBM, IBM Lotus, IBM WebSphere, IBM WebSphere Application Server, IT Networking, Java, Java Bean, Java Server Page (JSP), Lotus, Lotus Domino, Lotusscript, Messaging, Microsoft .NET Framework, Performance
ADVISORAMA People get so in the habit of worry that if you save them from drowning and put them on a bank to dry in the sun with hot chocolate and muffins they wonder whether they are catching cold. -- John Jay Chapman
|
|