Now we'll discuss techniques for invoking J2EE elements, such as servlets, JSPs, and EJBs from Domino applications presented over the Web. In this context, we are assuming that a Domino server is handling requests for Domino database elements (documents, forms, views, etc.) and that the J2EE elements are being invoked from the Domino elements ”for example, invoking a servlet to process a Domino form submission. In fact, let's start with just that scenario. Servlets/JSPs on Form Submission Since a JSP is a type of servlet, assume that you can replace "servlet" with "JSP" in what follows . To get a Domino form processed by a servlet, you must somehow invoke the servlet when the user hits the Submit button (or equivalent) on the form displayed in the user 's browser. There are two commonly used techniques: overriding the $$Return field or overriding the HTML form. The $$Return field override method makes use of the fact that the Domino server, in response to a form submit action ( document.forms[0].submit() ), sets the HTML response output as the contents of the $$Return field in the Domino form if this field exists. By setting the $$Return field to a URL string enclosed in brackets, the Domino server will invoke this URL. If the URL string is set to specify a servlet, the servlet is invoked as a result of the form submission. The servlet URL string can be set such that any of the form's field contents appear as parameters to the servlet. But note that these parameter values will be carried as clear text (not encrypted) in the servlet request URL string, so avoid passing sensitive data this way. The HTML form override method involves modifying the HTML generated by the Domino server for the form. The form HTML is modified to specify a secondary HTML form element with its METHOD and ACTION parameters set to the servlet to invoke on the submit action. To use this method, the programmer creates the Domino form with "pass-through HTML" text at the top of the form as in the following: </Form> <Form METHOD=POST ACTION="/servlet/MyServlet"> This HTML, when processed by the browser, ends the 0 th form object generated by Domino and starts a new form object ( forms[1] ) with the specified method and action parameters. Then, on a document.forms[1].submit() action, the servlet specified by the ACTION parameter is invoked. Since the fields on the Domino form are rendered by the browser as HTML input fields, they get passed to the servlet in the request header by the POST method (unless the fields had the HTML attribute " Type=Hidden " specified). Note that like the $$Return field method, the servlet parameter data is passed in "clear" text, so be careful what you pass as parameters. EJBs from Domino As discussed in a previous chapter on J2EE features, Enterprise Java Bean (EJB) components possess architectural features that make them well-suited for use in certain areas of application design, namely what's often referred to as "business logic" and persistent data storage. The two types of EJBs largely correspond to these uses: Session EJBs are used for business logic, and entity EJBs are used for persisting data. Given that EJBs are designed to be invoked in a distributed object, client/server fashion via a J2EE application server, can they be invoked via WAS from within Domino elements? The answer for Domino is similar to the answer for other applications ”it is often not easy nor flexible to invoke EJBs from outside of the J2EE application server, especially server to server. The reason lies with the communication protocol used for EJB client/server interaction. This EJB distributed object protocol is Internet Inter-ORB Protocol (IIOP), which is based on the Common Object Request Broker Architecture (CORBA). For Java, the CORBA/IIOP communication is encapsulated by the Remote Method Interaction (RMI) package. Thus, to utilize EJBs in a distributed fashion, you must communicate via Java RMI. The use of RMI often results in design issues and complexities. For example, RMI implementations entail certain restrictions on thread usage, which conflicts with that of the Domino agent manager JVM, and IIOP uses non-fixed TCP/IP ports, which are difficult to handle through firewalls. So even though it is possible to invoke EJBs directly from Domino Java code, it is usually not worth the design difficulties that ensue. As with other J2EE applications and frameworks, the simpler approach to utilizing EJBs from Domino is indirectly via servlets (JSPs), or more recently, by using Web services or JMS message queues (for message-driven EJBs). With these approaches, the code that needs to utilize the EJB invokes a servlet or Web service or puts a message to a queue, which, in turn , invokes the EJB. This way, the Domino-to-WAS communication is via HTTP, which is straightforward to code and passes through firewalls. Also the EJB interaction is entirely within the application server and can be made very efficient by the use of local EJB interfaces. Let's look at a specific example of how an EJB can be invoked via a servlet by a Domino agent and from a Domino form action. In this example, we make use of an entity EJB, BookBean , representing a single book published by a fictitious publishing house. Our EJB was developed within WSAD using the EJB creation and deployment wizards (see Chapter 8, "Development Tools," for references on how to develop EJBs within WSAD). The BookBean EJB is defined with a local home interface and with Container Managed Persistance (CMP). We deploy the EJB module containing BookBean (and its interface components) to a WTE server using the CloudScape RDBMS engine for the CMP implementation. Deploying the EJB module this way allows us to test the EJB under the WTE on our workstation without requiring access to an external database. Figure 9-4 shows the BookBean definition as viewed via the WSAD EJB Deployment Descriptor "Beans" page. Figure 9-4. BookBean EJB. Besides the basic EJB components generated by the WSAD wizards (those needed for the EJB to conform to the EJB 2.0 CMP specification), we add another finder method, findByTitle , to the local home interface via the EJB Query Language (EJBQL) feature of the EJB 2.0 CMP. This again is a wizard-driven procedure that results in the required Java code being added to the EJB components without any explicit Java coding from us. Next we add a servlet to the WSAD J2EE project containing our BookBean EJB module, again using a wizard. (We tell our children that being a modern-day software engineer is akin to being Harry Potter.) The servlet is named GetBook and provides the interface to the BookBean EJB via the GET request function (i.e., via the HTTPServlet.doGet() method ). Since we are working within the same J2EE project under WSAD, it is a simple matter to reference the EJB from the servlet: First, we add the BookBean EJB reference to the Web deployment descriptor. Then we add the Java code to the servlet to access the BookBean EJB via its local home interface. Figure 9-5 shows the References page from the Web deployment descriptor after adding the EJB reference. Figure 9-5. EJB reference from Web Deployment Descriptor. The following listing shows the servlet code. We chose to lookup the JNDI initial context and the EJB local home interface object in the servlet's initialization method, which is called only when the servlet is (re)loaded. Note that the JNDI name used for the initial context lookup must be of the form: java:comp/env/<EJB reference name> . import java.io.*; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.*; import javax.naming.*; /** * GetBook Servlet - Obtains data from Book EJB. * @version 1.0 */ public class GetBook extends HttpServlet { // Holds EJB Home interface BookLocalHome itsBookLH; /** * Handle the Get request - look for the Book EJB with the given id. * * @see javax.servlet.http.HttpServlet#void (javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ public void doGet( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException { PrintWriter aPW = resp.getWriter(); aPW.println( "<HTML>\n<HEAD>" + "\n<META http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">" + "\n<META http-equiv=\"Content-Style-Type\" content=\"text/css\">" + "\n<TITLE>GetBook Response</TITLE>" + "\n</HEAD> \n<BODY>\n" ); try { // Lookup the Book EJB via one of its finder parameters. // We look for the finder parameters as an argument of the GET // request. // 'id' = find by primary key. // 'title' = find by title. // BookLocal aBook = null; int aId; String aFinder = req.getParameter( "id" ); if ( aFinder != null ) { aId = Integer.parseInt( aFinder ); aBook = itsBookLH.findByPrimaryKey( new BookKey ( aId ) ); } else if ( (aFinder = req.getParameter( "title" )) != null ) { Collection aMatches = itsBookLH.findByTitle ( aFinder ); aBook = (BookLocal)aMatches.toArray()[0]; } else aPW.println( "<BR><B> Book finder argument not passed on request! </B><BR>" ); // Format Book EJB fields as HTML response. aId = ((BookKey)aBook.getPrimaryKey()).getItsId(); aPW.println( "<FONT color=\"#0000ff\" face=\"Arial\" size=\"+2\"><b>Book Details< /b></FONT>" + "\n<HR> <BR> ID Number: " + aId + "<BR>" + "<BR> Title: " + aBook.getItsTitle() + "<BR>" + "<BR> Author: " + aBook.getItsAuthor() + "<BR>" + "<BR> Date: " + aBook.getItsDate() + "<BR>" ); aPW.println( "<BR> Price: " + Float.toString( aBook.getItsPrice() ) + "<BR>" ); } catch ( Exception e ) { System.out.println( "doGet error: " + e ); aPW.println( "<br> Requested book not found. " ); aPW.println( "<br><br> Error: " + e ); } aPW.println( "</BODY>" ); aPW.close(); } /** * Initialize the servlet - set up the EJB home interface. * * @see javax.servlet.GenericServlet#void () */ public void init() throws ServletException { InitialContext initContext = null; String aJNDIName = "java:comp/env/BookLocalHome"; super.init(); // Set up EJB home interface for use by servlet threads. // Get the initial context try { System.out.println( "Getting the InitialContext..." ); initContext = new InitialContext(); } catch ( javax.naming.NamingException e ) { System.err.println("NamingException: " + e); // e.printStackTrace(); } // Perform the lookup try { System.out.println( "Performing the lookup.." ); Object obj = initContext.lookup( aJNDIName ); itsBookLH = (BookLocalHome)obj; } catch ( javax.naming.NamingException e ) { System.err.println( "Error retrieving the home interface: " + e ); // e.printStackTrace(); } } } Finally, we show an example of a Domino Java agent, which invokes the GetBook servlet. This code is a straightforward use of the java.net.URLConnection method to invoke a URL via an HTTP connection. After the Domino session and agent context objects are obtained, the agent accesses the document associated with the agent. If the agent is invoked from a document via a form action, hot-spot, or button, that would be the document itself. A java.net.URL object is constructed to represent the URL of the servlet to be invoked and its parameters. In this example, we invoke the servlet via the WTE, that is, via a URL specifying a host name of "localhost" and port 9080, and use the value of a field in the document as the servlet parameter. The servlet response text is read from the URLConnection object and can be processed by the agent code or, in the case where the agent was invoked via a browser, can be returned to the browser (via the PrintWriter object obtained from AgentBase.getAgentOutput() ). import lotus.domino.*; import java.io.*; import java.net.*; public class JavaAgent extends AgentBase { public void NotesMain() { // Get writer to agent's output (back to browser) PrintWriter aPW = getAgentOutput(); try { Session session = getSession(); AgentContext agentContext = session.getAgentContext(); Document aDoc = agentContext.getDocumentContext(); // Open URL for GetBook servlet. Use doc booktitle as argument to servlet. URL aURL = new URL( "http", "localhost", 9080, URLEncoder.encode( "/PublishingHouseWeb/GetBook?title=" + aDoc.getItemValue( "booktitle" ) ) ); URLConnection aCnctn = aURL.openConnection(); aCnctn.connect(); // Copy servlet output to StringBuffer StringWriter aSW = new StringWriter(); PrintWriter aPSW = new PrintWriter( aSW ); BufferedReader aRdr = new BufferedReader( new InputStreamReader( aCnctn.getInputStream() ) ); String aLine = aRdr.readLine(); while ( aLine != null ) { aPSW.println( aLine ); aLine = aRdr.readLine(); } // Here we could process the servlet's response... // For example, setting document fields with data from the response. aDoc.save(); } catch( Exception e ) { aPW.println( "Agent error: " + e ); } } } This same technique of invoking an EJB via a servlet also can be used anywhere JavaScript is written as part of a Domino design element. For example, the onClick event of a button may be written in JavaScript to invoke the EJB servlet. Using the GetBook servlet we created above, Figure 9-6 shows what JavaScript code looks like to invoke this servlet as the result of a button click. In this example, we obtain the servlet parameter text, the book's title, from the document (form) object. We replace any blank characters with "+'s" so that it can be passed as part of a URL and then set the "location" DOM object "href" property to the servlet URL. The servlet response is taken as the new page to display. Note that we specified "Common JavaScript" so that this code will work in both the Notes client as well as a browser. Figure 9-6. Invoking a servlet from JavaScript. |