Using the Common Client Interface

Up to now, today's lesson has concentrated on the system contract between an EIS and an application server. This information is important to assist your overall understanding of how Java applications interact with legacy and non-Java systems. But, as an application developer, you are probably eager to start writing code. This section of today's lesson looks at the relationship between an application component and a resource adapter. Specifically, you will learn how to code against the Common Client Interface (CCI) API.

A resource adapter provides a client API that you can code against. This client API may be an implementation of the CCI API, but the Connector architecture specification allows a resource adapter to support a client API that is specific to its underlying EIS. For example, an EIS that is a relational database might support the JDBC API. Even if the resource adapter does not provide a CCI implementation, a third-party can provide an implementation that sits above the resource adapter's EIS specific client API. Because of the wide variety of EISs and the APIs they may implement, today's lesson focuses on the CCI API. Always check with your resource adapter to determine the APIs it implements.

The CCI API provides a standard client API for you to code against. When you code against this API, you work with an abstraction of EIS functionality. This means that, like any other standard API, you only have to learn the one set of API calls to write code that interacts with multiple differing EISs. It also means that if you write code for one EIS and then change the underlying EIS, your code will still run against the replacement EIS.

Interacting with an EIS

The process of performing an operation against an EIS involves a number of steps. Figure 19.5 shows these steps.

Figure 19.5. The process of interacting with an EIS.


In a moment, you will write an application that implements these steps in code, but you will first explore them at a high-level. A resource adapter provides a ConnectionFactory that you use to create a Connection to the EIS. To locate the ConnectionFactory, you must first establish a JNDI Context for your current session and then perform a look up on the JNDI namespace to locate the ConnectionFactory. After you locate the ConnectionFactory, you can use it to create a Connection. As previously mentioned, the Connection is an application-level handle that you use to access an EIS instance.

After you have a Connection, you create an Interaction. An Interaction allows you to execute EIS functions. In other words, all the operations you want to perform against the EIS are done through the Interaction. Typically, when you execute an EIS function, you also provide the Interaction instance with a Record. The Record holds either the input to or the output from an EIS function. For example, you can use a Record instance to pass parameters to the EIS function, or you can use a Record instance to hold the information the EIS function returns.

If you are handling your own transaction management, you must begin a transaction before performing operations against the EIS. If you are not handling transaction management, you proceed to perform operations against the EIS. After you have performed the desired operations against the EIS, if you are managing transactions, you commit or rollback the transaction. Finally, you close the connection to the EIS.

Installing a Resource Adapter

To run the example applications in this lesson, you must install appropriate resource adapters and ensure that you have access to a corresponding EIS. To ensure that you can actually run and test the examples in today's lesson, both of the examples use the Cloudscape database, which you installed in Day 8, “Transactions and Persistence.” With regard to resource adapters, you will require two resource adapters—cciblackbox_tx and cciblackbox_xa. Both of these resource adapters are samples supplied with the J2EE reference implementation.

Resource adapters come packaged in Resource adapter ARrchive (RAR) files. When you downloaded and installed the J2EE SDK, you would have also installed the resource adapters for today's lesson. You can find these resource adapters in the lib/connector directory under the J2EE installation directory. If you do not have the resource adapters, you can download them by downloading the J2EE Connector architecture sample source and binary code adapters, which is available from Sun Microsystems at http://java.sun.com/j2ee/sdk_1.3/.

Note

RAR is also a recursive acronym for Roshall Archive a compressed file format. The format is named after its creator, Eugene Roshall, and you can discover more about it at http://www.rarsoft.com.


To install a resource adapter, start the J2EE server.

Use the deploytool to deploy the resource adapter. You must use the deployConnector switch and pass the location of the resource adapter and the server name. To do this on a Windows platform, type the following at the command prompt:

deploytool –deployConnector %J2EE_HOME%libconnectorcciblackbox-tx.rar localhost

On a Unix platform, use the following command:

deploytool –deployConnector $J2EE_HOME/lib/connector/cciblackbox-tx.rar localhost

To complete the installation, you must associate a connection factory with the deployed CCI adapter. You must use the j2eeadmin tool with the addConnectorFactory switch, passing two arguments—a JNDI name for the connection factory and the name of the resource adapter. To do this on a Windows platform, type the following at the command prompt:

j2eeadmin –addConnectorFactory eis/CciBlackBoxTx cciblackbox-tx.rar

On a Unix platform, use the following:

j2eeadmin –addConnectorFactory eis/CciBlackBoxTx cciblackbox-tx.rar

That's it, you have installed the first resource adapter. Repeat the process for the second resource adapter, cciblackbox-ax.rar. Finally, verify that you have correctly deployed the resource adapters by using the listConnectors switch of the deploytool. On both Windows and Unix platforms, type the following at the command line:

deployTool –listConnectors localhost

Creating a First CCI Application

This first application shows you how to write code that uses the CCI API to connect to an EIS. The application component that you will write in this instance is a Session bean; however, you could use any other J2EE component. The bean will contain the business logic that allows you to connect to an EIS and, using a stored procedure, insert an entry into a books table held by the Cloudscape database.

Note

If you are unfamiliar with stored procedures, they are methods that perform business logic, and are stored within a database. Typically, stored procedures are written as standard SQL statements. However, some databases can use stored procedures written in Java; Cloudscape is such a database.


In this first example, you are using an EJB that uses container-managed persistence. This ensures that access to the EIS does not occur outside the context of a transaction. As such, you do not have to explicitly handle transactions as shown in steps 6 and 8 of Figure 19.5. The second example in today's lesson shows you how to manage your own transactions.

To start coding this example, you define the bean's home interface. You can see that this interface is no different from any other home interface:

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface BookManagerHome extends EJBHome {
    BookManager create() throws RemoteException, CreateException;
}

Like the home interface, the remote interface is no different to any other remote interface. As you can see, the interface defines the insertBook() method that the client will use to insert a book into the EIS database:

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface BookManager extends EJBObject {
    public void insertBook(String name, double price) throws RemoteException;
}

After you create the interfaces, you can start creating the EJB class. The first thing to note about this class is that you import two additional packages and an additional class:

  • javax.resource.cci The package containing the CCI API interfaces

  • javax.resource.ResourceException The root exception of the Connector architecture's exception hierarchy

  • com.sun.connector.cciblackbox

The code itself declares four fields, which today's lesson discusses as you use them:

public class BookManagerEJB implements SessionBean {
    private SessionContext sc;
    private String user;
    private String pass;
    private ConnectionFactory cf;

In this example, there is no implementation of the ejbCreate(), ejbRemove(), ejbActivate(), and ejbPassivate() methods. Listing 19.1 at the end of this section shows their empty bodies. However, you must provide a body for the setSessionContext() method:

public void setSessionContext(SessionContext sc) {
    this.sc=sc;
    try {
        Context ic = new InitialContext();
        user = (String) ic.lookup("java:comp/env/user");
        pass = (String) ic.lookup("java:comp/env/password");
        cf = (ConnectionFactory) ic.lookup("java:comp/env/CCIEIS");
    }
    catch (NamingException ne) {
        System.err.println(ne.getMessage());
    }
}

The try-catch construct contains the code that uses the CCI API. The code starts by establishing a JNDI context (step 1 of Figure 19.5) using code with which you are familiar. You then use the JNDI context to perform three lookups (step 2 of Figure 19.5). The first two simply obtain the username and password from environment properties. The third obtains a reference to the connection factory for the resource adapter. Finally, the lookup() method throws a NamingException, so you catch this.

After you have implemented the setSessionContext() method, you can write the methods that contain the business logic. In this example, there is only one such method and it accepts a book name (String) and a book price (double), that it inserts into the EIS:

public void insertBook(String name, double price) {

A try-catch construct encapsulates the method body, because many of the methods you call might throw ResourceExceptions or exceptions that extend ResourceException. The construct begins by creating a new connection to the EIS (step 3 of Figure 19.5). To do this, you must first create a ConnectionSpec object that you will use to pass the username and password to the connection factory. ConnectionSpec is an interface, so you create an object by using the concrete class CciConnectionSpec:

ConnectionSpec cs = new CciConnectionSpec(user, pass);

After you create the ConnectionSpec object, you can get a Connection object by using the getConnection() method of the ConnectionFactory. This method is overloaded and, as such, has two versions, both of which return a Connection object. The first version accepts no parameters, and you should only use this if you want the EIS to manage the sign-on process. The second version, which you will now use, accepts a single parameter—a ConnectionSpec object. The method throws a ResourceException.

Connection c = cf.getConnection(cs);

To actually perform operations against the EIS, you need an Interaction object (step 4 of Figure 19.5). To get an Interaction object, you call the createInteraction() method of the Connection object. Note that the methods throws a ResourceException:

Interaction i = c.createInteraction();

To use the Interaction object, you must create an InteractionSpec object. This object allows the Interaction object to execute functions on the underlying EIS. The InteractionSpec interface in the CCI API exposes three fields and no methods. An implementation of the interface does not have to support these standard fields if they do not apply to the underlying EIS. In addition, the implementation can provide any additional fields that apply to the EIS. The specification for the interface states that the implementation must provide accessor methods (get and set) for any fields that it does support. The implementation of this interface that comes with cciblackbox provides accessor methods for three fields:

  • functionName

  • schema

  • catalog

For the sake of completeness, you set all three of these fields in the example you are currently writing. Note, however, that the catalog is set to null, because Cloudscape does not require a catalog name. The INSERTBOOK function name refers to the stored procedure that you will execute in a moment. You will learn more about stored procedures a little later in this section.

CciInteractionSpec iSpec = new CciInteractionSpec();
iSpec.setFunctionName("INSERTBOOK");
iSpec.setSchema(user);
iSpec.setCatalog(null);

You are now ready to start creating records that hold the input to or the output from an EIS function. To create records, you must first create a record factory. The getRecordFactory() method of the ConnectionFactory object creates a RecordFactory. This method throws two exceptions—a ResourceException and one of its subclasses, NotSupportedException—thrown when a resource adapter or application server does not support the operation.

RecordFactory rf = cf.getRecordFactory();

The RecordFactory creates two types of records—MappedRecord and IndexedRecord. A MappedRecord is a record that you use to hold a key-value representation of the record elements. You should find this type of record familiar because it has a super interface of java.util.Map (HashMap and Hashtable also implement this interface). In contrast to the MappedRecord, the IndexedRecord is a record that represents record elements as an ordered collection. This type of record allows you to access items in the collection by index, and it also allows you to search for elements within the collection. You should also find using this type of record familiar because it has a super interface of java.util.List (Vector and ArrayList also implement this interface). In the example you are writing, you use an IndexedRecord because Cloudscape only supports indexed records.

IndexedRecord iRec = rf.createIndexedRecord("InputRecord");

The stored procedure that you are going to execute on the EIS accepts two parameters—a book name and a book price. You use the IndexedRecord object that you just created to pass these parameters to the procedure. To do this, you use the add() method that IndexedRecord inherits from java.util.List. The method returns a boolean that you assign to a variable named flag. In this instance, you discard the returned Boolean because you do not use it later in the code:

iRec.add(name);
iRec.add(new Double(price));

You can see that the code creates a new instance of Double rather than passes the price as a primitive double. The reason for creating the Double object is that when the program connects to the EIS, it converts Java object types into SQL equivalents, and when the EIS returns, the program converts the SQL type back to a Java type. The actual mapping of Java and SQL types is defined in the Types class of the java.sql package. To gain a complete reference to the Java-SQL mappings, refer to the J2SE API documentation.

Note

The stored procedures used in this example are written in Java. It is not the objective of this lesson to explain stored procedures, but if you want to view their code, you can find them on the CD-ROM that accompanies this book.


Now that you have added the parameters to the record, you can execute the function on the EIS. To do this, you use the execute() method of the Interaction object. This method is overloaded and, as such, comes in two varieties. The one you will use accepts an Interaction object and an input (containing parameters) Record object. When the function executes, the method returns a Record object containing the output, such as the results of a query. The second version of the method accepts an additional Record object that the method updates to include the output from the function execution. This version of the method returns a Boolean that has a value of true if the execution of the EIS function was successful.

i.execute(iSpec, iRec);

After you execute the EIS function, you can close the connection to the underlying EIS (step 9 in Figure 19.5). To do this, simply call the close() method of the Connection object:

c.close();

You have now completed the Session bean code to insert data into an EIS. Listing 19.1 shows the complete code.

Listing 19.1. BookManagerEJB.java
import javax.ejb.*;
import javax.resource.cci.*;
import javax.resource.ResourceException;
import javax.naming.*;
import com.sun.connector.cciblackbox.*;

public class BookManagerEJB implements SessionBean {
    private SessionContext sc;
    private String user;
    private String pass;
    private ConnectionFactory cf;

    // Session Bean Methods
    public void ejbPassivate() {}
    public void ejbActivate() {}
    public void ejbCreate() {}
    public void ejbRemove() {}
    public void setSessionContext(SessionContext sc) {
        this.sc=sc;
        try {
            // Establish a JNDI intial context
            Context ic = new InitialContext();
            // Lookup the username and password
            user = (String) ic.lookup("java:comp/env/user");
            pass = (String) ic.lookup("java:comp/env/password");
            // Lookup the connection factory
            cf = (ConnectionFactory) ic.lookup("java:comp/env/CCIEIS");
        }
        catch (NamingException ne) {
            System.err.println(ne.getMessage());
        }
    }

    // The business method
    public void insertBook(String name, double price) {
        try {
            // Create a ConnectionSpec object that holds username and password
            ConnectionSpec cs = new CciConnectionSpec(user, pass);

            // Get a connection to the EIS from the ConnectionFactory
            Connection c = cf.getConnection(cs);

            // Create an interaction, to invoke stored procedures
            Interaction i = c.createInteraction();

            /**
             * Create an InteractionSpec,
             * so as to pass properties to the interaction object
             */
            CciInteractionSpec iSpec = new CciInteractionSpec();

            // Set the fields for this instance
            iSpec.setFunctionName("INSERTBOOK");
            iSpec.setSchema(user);
            iSpec.setCatalog(null);

            // Create a new record factory from the connection factory
            RecordFactory rf = cf.getRecordFactory();

            // Cloudscape only supports indexed records, so create one of these
            IndexedRecord iRec = rf.createIndexedRecord("InputRecord");

            // Add the name and price parameters or the record
            iRec.add(name);
            iRec.add(new Double(price));

            // Execute the stored procedure
            i.execute(iSpec, iRec);

            // Close the connection
            c.close();
        }
        catch(ResourceException re) {
            System.err.println(re.getMessage());
        }
    }
}
						

To run the application, you must compile the EJB's source code, build the application, and write a client application. The client for this example is quite simple; it accesses the bean as you would any other bean, namely

  • Create a new context

  • Lookup the EJB

  • Create instances of the home interface and the EJB itself

After you create an instance of the EJB, you call its insertBook() method passing a book name and book price:

System.err.println("Inserting book...");
bm.insertBook("Teach Yourself in 21 Days", 29.99);

That's it. To run the application, compile Listing 19.2.

Listing 19.2. BookManagerClient.java
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public class BookManagerClient {
    public static void main(String[] args) {
        try {
            Context initial = new InitialContext();
            Object objref = initial.lookup("java:comp/env/ejb/BookManager");

            BookManagerHome bmh = (BookManagerHome) PortableRemoteObject.narrow(objref,
 BookManagerHome.class);

            BookManager bm = bmh.create();

            System.err.println("Inserting book...");
            bm.insertBook("Teach Yourself in 21 Days", 29.99);

            System.err.println("Book inserted.");
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
}
						

Managing Transactions and Exploring Records

The previous example application was relatively simple; it used container-managed persistence and defined a single business method. This next example defines a method that retrieves data from the underlying EIS, and also requires you to manage transactions.

The EJB's home interface is identical to that in the previous example, except that you append a 2 to the interface and classnames:

import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface BookManagerHome2 extends EJBHome {
    BookManager2 create() throws RemoteException, CreateException;
}

The EJB's remote interface is also very similar to that in the previous example. The important thing to note is that the method signature the interface defines now applies to a listTitles() method that accepts only a single parameter, a string:

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface BookManager2 extends EJBObject {
    public void listTitles(String name) throws RemoteException;
}

All of the changes to the main EJB class apply to the business method, except that you must import the java.util package because you will use this later. The new business method has a signature of

public void listTitles(String name)

Like the previous example, a try-catch construct encapsulates the body of the method. Also exactly like the previous example, you create ConnectionSpec, Connection, Interaction, CciInteractionSpec, RecordFactory, and IndexedRecord objects:

ConnectionSpec cs = new CciConnectionSpec(user, pass);
Connection c = cf.getConnection(cs);
Interaction i = c.createInteraction();
CciInteractionSpec iSpec = new CciInteractionSpec();
iSpec.setFunctionName("LISTTITLES");
iSpec.setSchema(user);
iSpec.setCatalog(null);
RecordFactory rf = cf.getRecordFactory();
IndexedRecord iRec = rf.createIndexedRecord("InputRecord");

After you create the IndexedRecord object, the code differs in a number of respects from the previous example. The first change is that you only add one parameter to the input record—the stored procedure only searches for book titles, which contain the following parameter value:

iRec.add(name);

Now that you have created and populated the input record, you should start a transaction so you can roll back to a convenient point in the case of an error. Earlier, today's lesson discussed transaction management, and it said that there were two ways to manage local transactions, either by:

  • Container management—like the previous example

  • Component management

In this example, you are implementing component transaction management. To do this, you use the methods the CCI API LocalTransaction interface defines. You should note that it is optional for a CCI implementation to implement this interface. To get a LocalTransaction instance, you call the getLocalTransaction() method of the Connection object. This method throws a RemoteException and, in the instance of an implementation not supporting local transactions, it throws a NotSupportedException.

LocalTransaction lTrans = c.getLocalTransaction();

After you obtain a LocalTransaction object, you call its begin() method to begin a transaction:

lTrans.begin();

At this point in the previous example, you invoked a stored procedure that inserted a record into the underlying EIS. In this example, you execute a stored procedure that performs a SELECT against the EIS. Because you are changing the business logic, the code changes in a couple respects. First, because the execution of the stored procedure might fail, you use a try-catch construct to encapsulate the code that executes the stored procedure. Second, you create a record to hold the output that the stored procedure returns. You can either create a Record and pass this as a third parameter to the execute() method of the Interaction object, or you can simply use the version of the execute() method that returns a Record:

Record oRec = i.execute(iSpec, iRec);

The Record object, oRec, contains the results of the SELECT statement the stored procedure executed against the underlying EIS. To iterate through the list of elements the Record object contains, you create an Iterator object. The IndexedRecord class inherits an iterator() method from java.util.List, and this returns an Iterator object. To call this method, you must cast the Record object you just created to an IndexedRecord:

Iterator iterator = ((IndexedRecord)oRec).iterator();

Now that you have an Iterator object, you can iterate through the data returned by the EIS and print each of the book titles. You use the Iterator object in the same way as you would in any other Java application:

while (iterator.hasNext()) {
    String title = (String)iterator.next();
    System.out.println(title);
}

That completes the processing undertaken within the try element of the try-catch construct. To complete the code, you must catch any exceptions that might be thrown and, in such an event, rollback the transaction by using the rollback() method of the LocalTransaction:

catch (ResourceException e) {
    lTrans.rollback();
}

You have now completed the Session bean code to retrieve data from an EIS. Listing 19.3 shows the complete code.

Listing 19.3. BookManagerEJB2.java
import javax.ejb.*;
import javax.resource.cci.*;
import javax.resource.ResourceException;
import javax.naming.*;
import com.sun.connector.cciblackbox.*;
import java.util.*;

public class BookManagerEJB2 implements SessionBean {
    private SessionContext sc;
    private String user;
    private String pass;
    private ConnectionFactory cf;

    // Session Bean Methods
    public void ejbPassivate() {;}
    public void ejbActivate() {;}
    public void ejbCreate() {;}
    public void ejbRemove() {;}
    public void setSessionContext(SessionContext sc) {
        this.sc=sc;
        try {
            Context ic = new InitialContext();
            user = (String) ic.lookup("java:comp/env/user");
            pass = (String) ic.lookup("java:comp/env/password");
            cf = (ConnectionFactory) ic.lookup("java:comp/env/CCIEIS");
        }
        catch (NamingException ne) {
            System.err.println(ne.getMessage());
        }
    }
    // The business method
    public void listTitles(String name) {
        try {
            ConnectionSpec cs = new CciConnectionSpec(user, pass);
            Connection c = cf.getConnection(cs);
            Interaction i = c.createInteraction();
            CciInteractionSpec iSpec = new CciInteractionSpec();
            iSpec.setFunctionName("LISTTITLES");
            iSpec.setSchema(user);
            iSpec.setCatalog(null);
            RecordFactory rf = cf.getRecordFactory();
            IndexedRecord iRec = rf.createIndexedRecord("InputRecord");

            // Add the name parameter to the record
            iRec.add(name);

            // Obtain a reference to a transaction context
            LocalTransaction lTrans = c.getLocalTransaction();

            // Start the transaction
            lTrans.begin();
/**
             * Execute the stored procedure,
             * passing the InteractionSpec and the record
             */
            try {
                Record oRec = i.execute(iSpec, iRec);
                // Get an iterator. Cast the Record to an IndexedRecord.
                Iterator iterator = ((IndexedRecord)oRec).iterator();
                // Iterate through the records and print names
                while (iterator.hasNext()) {
                    String title = (String)iterator.next();
                    System.out.println(title);
                }
            }
            catch (ResourceException e) {
                // Rollback in the event of an error
                lTrans.rollback();
            }
            c.close();
        }
        catch(ResourceException re) {
            System.err.println(re.getMessage());
        }
    }
}
						

Finally, to run the application, you must compile the EJB's source code, build the application, and write a client application. The client for this example is the same as that for the previous example, except that it calls the EJB's listTitles() method.

That's it. To run the application, compile the code in Listing 19.4.

Listing 19.4. BookManagerClient2.java
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;

public class BookManagerClient2 {
    public static void main(String[] args) {
        try {
            Context initial = new InitialContext();
            Object objref = initial.lookup("java:comp/env/ejb/BookManager2");
            BookManagerHome2 bmh = (BookManagerHome2) PortableRemoteObject.narrow(objref,
 BookManagerHome2.class);
            BookManager2 bm = bmh.create();
            System.out.println("Retrieving titles...");
            bm.listTitles("Sams");
            System.out.println("Completed Retrieval.");
        }
        catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
}
						

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.24.106