|
Home TOC Index |
|
Search
Feedback |
Common Client Interface
This section describes how components use the Connector architecture Common Client Interface (CCI) API and a resource adapter to access data from an EIS.
Overview of the CCI
Defined by the J2EE Connector Architecture specification, the CCI defines a set of interfaces and classes whose methods allow a client to perform typical data access operations. Our example
CoffeeEJBsession bean includes methods that illustrate how to use the CCI, in particular, the following CCI interfaces and classes:
ConnectionFactory: Provides an application component with aConnectioninstance to an EIS.Connection: Represents the connection to the underlying EIS.ConnectionSpec: Provides a means for an application component to pass connection-request-specific properties to theConnectionFactorywhen making a connection request.Interaction: Provides a means for an application component to execute EIS functions, such as database stored procedures.InteractionSpec: Holds properties pertaining to an application component's interaction with an EIS.Record: The superclass for the different kinds of record instances. Record instances may beMappedRecord,IndexedRecord, orResultSetinstances, which all inherit from theRecordinterface.RecordFactory: Provides an application component with aRecordinstance.IndexedRecord: Represents an ordered collection ofRecordinstances based on thejava.util.Listinterface.A client or application component that uses the CCI to interact with an underlying EIS does so in a prescribed manner. The component must establish a connection to the EIS's resource manager, and it does so using the
ConnectionFactory. TheConnectionobject represents the actual connection to the EIS and is used for subsequent interactions with the EIS.The component performs its interactions with the EIS, such as accessing data from a specific table, using an
Interactionobject. The application component defines theInteractionobject using anInteractionSpecobject. When the application component reads data from the EIS (such as from database tables) or writes to those tables, it does so using a particular type ofRecordinstance, either aMappedRecord,IndexedRecord, orResultSetinstance. Just as theConnectionFactorycreatesConnectioninstances, aRecordFactorycreatesRecordinstances.Our example shows how a session bean uses a resource adapter to add and read records in a relational database. The example shows how to invoke stored procedures, which are business logic functions stored in a database and specific to an enterprise's operation. Stored procedures consist of SQL code to perform operations related to the business needs of an organization. They are kept in the database and can be invoked when needed, just as you might invoke a Java
method. In addition to showing how to use the CCI to invoke stored procedures, we'll also explain how to pass parameters to stored procedures and how to map the parameter data types from SQL to those of the Java programming language.
Programming with the CCI
The code for the following example is in the
examples/src/connector/ccidirectory.To illustrate how to use a CCI resource adapter, we've written a session bean and a client of that bean. These pieces of code illustrate how clients invoke the different CCI methods that resource adapters built on CCI might make available. Our example uses the two sample CCI-specific resource adapters:
cciblackbox_tx.rarand cciblackbox_xa.rar.The
Coffeesession bean is much like any other session bean. It has a home interface (CoffeeHome), a remote interface (Coffee), and an implementation class (CoffeeEJB). To keep things simple, we've called the clientCoffeeClient.Let's start with the session bean interfaces and classes. The home interface,
CoffeeHome, is like any other session bean home interface. It extendsEJBHomeand defines acreatemethod to return a reference to theCoffeeremote interface.The
Coffeeremote interface defines the bean's two methods that may be called by a client.public void insertCoffee(String name, int quantity) throws RemoteException; public int getCoffeeCount() throws RemoteException;Now let's examine the
CoffeeEJBsession bean implementation class to see how it uses the CCI. To begin with, notice thatCoffeeEJBimports thejavax.resourceCCI interfaces and classes, along with thejavax.resource.ResourceExceptionand the samplecciblackboxclasses.import javax.resource.cci.*; import javax.resource.ResourceException; import com.sun.connector.cciblackbox.*; Obtaining a Database ConnectionPrior to obtaining a database connection, the session bean does some set up work in its
setSessionContextmethod. (See the following code example.) Specifically, thesetSessionContextmethod sets the user and password values, and instantiates aConnectionFactory. These values and objects remain available to the other session bean methods. (In this and subsequent code examples, the numbers in the left margin correspond to the explanation that follows the code.)public void setSessionContext(SessionContext sc) { try { this.sc = sc; 1 Context ic = new InitialContext(); 2 user = (String) ic.lookup("java:comp/env/user"); password = (String) ic.lookup ("java:comp/env/password"); 3 cf = (ConnectionFactory) ic.lookup ("java:comp/env/CCIEIS"); } catch (NamingException ex) { ex.printStackTrace(); } }
- Establish a JNDI
InitialContext.- Use the JNDI
InitialContext.lookupmethod to find the user and password values.- Use the
lookupmethod to locate theConnectionFactoryfor the CCI black box resource adapter and obtain a reference to it.
CoffeeEJBuses its private methodgetCCIConnectionto establish a connection to the underlying resource manager or database. A client of theCoffeesession bean cannot invoke this method directly. Rather, the session bean uses this method internally to establish a connection to the database. The following code uses the CCI to establish a database connection.private Connection getCCIConnection() { Connection con = null; try { 1 ConnectionSpec spec = new CciConnectionSpec(user, password); 2 con = cf.getConnection(spec); } catch (ResourceException ex) { ex.printStackTrace(); } return con; }
- Instantiate a new
CciConnectionSpecobject with the user and password values obtained by thesetSessionContextmethod. TheCciConnectionSpecclass is the implementation of theConnectionSpecinterface.- Call the
ConnectionFactory.getConnectionmethod to obtain a connection to the database. (The reference to theConnectionFactorywas obtained in thesetSessionContextmethod.) Use theCciConnectionSpecobject to pass the required properties to theConnectionFactory. ThegetConnectionmethod returns aConnectionobject.The
CoffeeEJBbean also includes a private method,closeCCIConnection, to close a connection. The method invokes theConnectionobject'sclosemethod from within atry/catchblock. Like thegetCCIConnectionmethod, this is a private method intended to be called from within the session bean.private void closeCCIConnection(Connection con) { try { con.close(); } catch (ResourceException ex) { ex.printStackTrace(); } }Database Stored Procedures
The sample CCI black box adapters call database stored procedures. It is important to understand stored procedures before delving into how to read or write data using the sample CCI black box adapters. The methods of these sample CCI adapters do not actually read data from a database or update database data. Instead, these sample CCI adapters enable you to invoke database stored procedures, and it is the stored procedures that actually read or write to the database.
A stored procedure is a business logic method or function that is stored in a database and is specific for the enterprise's business. Typically, stored procedures consist of SQL code, though in certain cases (such as with Cloudscape) they may consist of code written in the Java
programming language. Stored procedures perform operations related to the business needs of an organization. They are kept in the database, and applications can invoke them when needed.
Stored procedures are typically SQL statements. Our example calls two stored procedures:
COUNTCOFFEEandINSERTCOFFEE. TheCOUNTCOFFEEprocedure merely counts the number of coffee records in theCoffeetable, as follows:SELECT COUNT(*) FROM COFFEEThe
INSERTCOFFFEEprocedure adds a record with two values, passed to the procedure as parameters, to the sameCoffeetable, as follows:INSERT INTO COFFEE VALUES (?,?)Mapping to Stored Procedure Parameters
When you invoke a stored procedure from your application component, you may have to pass argument values to the procedure. For example, when you invoke the
INSERTCOFFEEprocedure, you pass it two values for theCoffeerecord elements. Likewise, you must be prepared to receive values that a stored procedure returns.The stored procedure, in turn, passes its set of parameters to the database management system (DBMS) to carry out its operation and may receive values back from the DBMS. Database stored procedures specify, for each of their parameters, the SQL type of the parameter value and the mode of the parameter. Mode can be input (
IN), output (OUT), or both input and output (INOUT). An input parameter only passes data in to the DBMS, and an output parameter only receives data back from the DBMS. AnINOUTparameter accepts both input and output data.When you use the CCI
executemethod to invoke a database stored procedure you also create an instance of anInputRecord, provided that you're passing a parameter to the stored procedure and that the stored procedure you're executing returns data (possibly anOutputRecordinstance). TheInputRecordandOutputRecordare instances of the supportedRecordtypes:IndexedRecord,MappedRecord, orResultSet. In our example, we instantiate anInputRecordand anOutputRecordthat are bothIndexedRecordinstances.
Note: The CCI black box adapters only supportIndexedRecordtypes.
The
InputRecordmaps theINandINOUTparameters for the stored procedure, and theOutputRecordmaps theOUTandINOUTparameters. Each element of an input or output record corresponds to a stored procedure parameter. That is, there is an entry in theInputRecordfor eachINandINOUTparameter declared in the stored procedure. Not only does theInputRecordhave the same number of elements as the procedure's input parameters, but they also are declared in the same order as in the procedure's parameter list. The same holds true for theOutputRecord, though its list of elements matches only theOUTandINOUTparameters. For example, suppose you have a stored procedureXthat declares three parameters. The first parameter is anINparameter, the second is anOUTparameter, and the third is anINOUTparameter. Figure 17-2 shows how the elements of anInputRecordand anOutputRecordmap to this stored procedure.Figure 17-2 Mapping Stored Procedure Parameters to CCI Record Elements
When you use the CCI black box adapter, you designate the parameter type and mode in the same way, though the underlying Oracle or Cloudscape DBMS declares the mode differently. Oracle designates the parameter's mode in the stored procedure declaration, along with the parameter's type declaration. For example, an Oracle
INSERTCOFFEEprocedure declares its twoINparameters as follows:procedure INSERTCOFFEE (name IN VARCHAR2, qty IN INTEGER)An Oracle
COUNTCOFFEEprocedure declares its parameterNas anOUTparameter:procedure COUNTCOFFEE (N OUT INTEGER)Cloudscape, which declares a stored procedure as a method signature in the Java
programming language, indicates an
INparameter using a single value, and anINOUTparameter using an array. The method's return value is theOUTparameter. For example, Cloudscape declares theINparameters (nameandqty) forinsertCoffeeand theOUTparameter (the method's return value) forcountCoffeeas follows:public static void insertCoffee(String name, int qty) public int countCoffee()If
qtywere anINOUTparameter, then Cloudscape would declares it aspublic static void insertCoffee(String name, int[] qty)procedure INSERTCOFFEE (name IN VARCHAR2, qty INOUT INTEGER)You must also map the SQL type of each value to its corresponding Java type. Thus, if the SQL type is an integer, then the
InputRecordorOutputRecordelement must be defined as anIntegerobject. If the SQL type is aVARCHAR, then the Java type must be aStringobject. Thus, when you add the element to theRecord, you declare it to be an object of the proper type. For example, add an integer and a string element to anInputRecordas follows:iRec.add (new Integer (intval)); iRec.add (new String ("Mocha Java"));
Note: The JDBC Specification defines the type mapping of SQL and the Java programming language.
Reading Database Records
The
getCoffeeCountmethod ofCoffeeEJBillustrates how to use the CCI to read records from a database table. This method does not directly read the database records itself; instead, it invokes a procedure stored in the database calledCOUNTCOFFEE. It is the stored procedure that actually reads the records in the database table.The CCI provides interfaces for three types of records:
IndexedRecord,MappedRecord, andResultSet. These three record types inherit from the base interface,Record. They differ only in how they map the record elements within the record. Our example usesIndexedRecord, which is the only record type currently supported.IndexedRecordholds its record elements in an ordered, indexed collection based onjava.util.List. As a result, we use anIteratorobject to access the individual elements in the list.Let's begin by looking at how the
getCoffeeCountmethod uses the CCI to invoke a database stored procedure. Again, note that the numbers in the margin to the left of the code correspond to the explanation after the code example.public int getCoffeeCount() { int count = -1; try { 1 Connection con = getCCIConnection(); 2 Interaction ix = con.createInteraction(); 3 CciInteractionSpec iSpec = new CciInteractionSpec(); 4 iSpec.setSchema(user); iSpec.setCatalog(null); iSpec.setFunctionName("COUNTCOFFEE"); 5 RecordFactory rf = cf.getRecordFactory(); 6 IndexedRecord iRec = rf.createIndexedRecord("InputRecord"); 7 Record oRec = ix.execute(iSpec, iRec); 8 Iterator iterator = ((IndexedRecord)oRec).iterator(); 9 while(iterator.hasNext()) { Object obj = iterator.next(); if(obj instanceof Integer) { count = ((Integer)obj).intValue(); } else if(obj instanceof BigDecimal) { count = ((BigDecimal)obj).intValue(); } } 10 closeCCIConnection(con); }catch(ResourceException ex) { ex.printStackTrace(); } return count; }
- Obtain a connection to the database.
- Create a new
Interactioninstance. ThegetCoffeeCountmethod creates a newInteractioninstance because it is this object that enables the session bean to execute EIS functions such as invoking stored procedures.- Instantiate a
CciInteractionSpecobject. The session bean must pass certain properties to theInteractionobject, such as schema name, catalog name, and the name of the stored procedure. It does this by instantiating aCciInteractionSpecobject. TheCciInteractionSpecis the implementation class for theInteractionSpecinterface, and it holds properties required by theInteractionobject to interact with an EIS instance. (Note that our example uses a Cloudscape database, which does not require a catalog name.)- Set values for the
CciInteractionSpecinstance's fields. The session bean uses theCciInteractionSpecmethodssetSchema,setCatalog, andsetFunctionNameto set the required values into the instance's fields. Our example passesCOUNTCOFFEEtosetFunctionNamebecause this is the name of the stored procedure it intends to invoke.- The
getCoffeeCountmethod uses theConnectionFactoryto obtain a reference to aRecordFactoryso that it can create anIndexedRecordinstance. We obtain anIndexedRecord(or aMappedRecordor aResultSet) using aRecordFactory.- Invoke the
createIndexedRecordmethod ofRecordFactory. This method creates a newIndexedRecordusing the nameInputRecord, which is passed to it as an argument.- The
getCoffeeCountmethod has completed the required set-up work and can invoke the stored procedureCOUNTCOFFEE. It does this using theInteractioninstance'sexecutemethod. Notice that it passes two objects to theexecutemethod: theInteractionSpecobject, whose properties reference theCOUNTCOFFEEstored procedure, and theIndexedRecordobject, which the method expects to be an inputRecord. Theexecutemethod returns an outputRecordobject.- The
getCoffeeCountmethod uses anIteratorto retrieve the individual elements from the returnedIndexedRecord. It casts the outputRecordobject to anIndexedRecord.IndexedRecordcontains an iterator method that it inherits fromjava.util.List.- Retrieve each element in the returned record object using the
iterator.hasNextmethod. Each extracted element is anObject, and the bean evaluates whether it is an integer or decimal value and processes it accordingly.- Close the connection to the database.
Inserting Database Records
The
CoffeeEJBsession bean implements theinsertCoffeemethod to add new records into theCoffeedatabase table. This method invokes theINSERTCOFFEEstored procedure, which inserts a record with the values (nameandqty) passed to it as arguments.The
insertCoffeemethod shown here illustrates how to use the CCI to invoke a stored procedure that expects to be passed argument values. This example shows the code for theinsertCoffeemethod and is followed by an explanation.public void insertCoffee(String name, int qty) { try { 1 Connection con = getCCIConnection(); 2 Interaction ix = con.createInteraction(); 3 CciInteractionSpec iSpec = new CciInteractionSpec(); 4 iSpec.setFunctionName("INSERTCOFFEE"); iSpec.setSchema(user); iSpec.setCatalog(null); 5 RecordFactory rf = cf.getRecordFactory(); 6 IndexedRecord iRec = rf.createIndexedRecord("InputRecord"); 7 boolean flag = iRec.add(name); flag = iRec.add(new Integer(qty)); 8 ix.execute(iSpec, iRec); 9 closeCCIConnection(con); }catch(ResourceException ex) { ex.printStackTrace(); } }
- Establish a connection to the database.
- Create a new
Interactioninstance for the connection so that the bean can execute the database's stored procedures.- Instantiate a
CciInteractionSpecobject so that the bean can pass the necessary properties--schema name, catalog name, and stored procedure name--to theInteractionobject. TheCciInteractionSpecclass implements theInteractionSpecinterface and holds properties that theInteractionobject requires to communicate with the database instance.- Set the required values into the new
CciInteractionSpecinstance's fields, using the instance'ssetSchema,setCatalog, andsetFunctionNamemethods. Our example passesINSERTCOFFEEtosetFunctionName, andusertosetSchema.- Obtain a reference to a
RecordFactoryusing theConnectionFactoryobject'sgetRecordFactorymethod.- Invoke the
RecordFactoryobject'screateIndexedRecordmethod to create a newIndexedRecordwith the nameInputRecord.- Use the
IndexedRecordaddmethod to set the values for the two elements in the new record. Call theaddmethod once for each element. Our example sets the first record element to thenamevalue and the second element to theqtyvalue. Notice thatqtyis set to anIntegerobject when passed to theaddmethod. TheCoffeeEJBsession bean is now ready to add the new record to the database.- Call the
Interactioninstance'sexecutemethod to invoke the stored procedureINSERTCOFFEE. Just as we did when invoking theCOUNTCOFFEEprocedure, we pass two objects to theexecutemethod: theInteractionSpecobject with the correctly set properties for theINSERTCOFFEEstored procedure, and theIndexedRecordobject representing an inputRecord. Theexecutemethod is not expected to return anything in this case.- Close the connection to the database.
Writing a CCI Client
A client application that relies on a CCI resource adapter is very much like any other J2EE client that uses enterprise bean methods. Our
CoffeeClientapplication uses the methods of theCoffeeEJBsession bean to access theCoffeetable in the underlying database.CoffeeClientinvokes theCoffee.getCoffeeCountmethod to read theCoffeetable records and invokes theCoffee.insertCoffeemethod to add records to the table.CCI Tutorial
This tutorial shows how to deploy and test the sample CCI black box adapter with the code described in the preceding sections. This code has been packaged into a J2EE application EAR file named
CoffeeApp.ear, which is located in thej2eetutorial/examples/earsdirectory. The source code is inj2eetutorial/examples/src/connector/cci. To compile the source code, go to thej2eetutorial/examplesdirectory and typeant cci.Deploying the Resource Adapter
- Use the
deploytoolutility to deploy the CCI black box resource adapter. Specify the name of the resource adapter's RAR file (cciblackbox-tx.rar), plus the name of the server (localhost).
- UNIX
deploytool -deployConnector \ $J2EE_HOME/lib/connector/cciblackbox-tx.rar localhost- Windows (Note that this command and all subsequent Windows commands must be entered on a single line.)
deploytool -deployConnector %J2EE_HOME%\lib\connector\cciblackbox-tx.rar localhost- Next, add a connection factory for the deployed CCI adapter. The connection factory supplies a data source connection for the adapter. Use
j2eeadminto create the connection factory, specifying the adapter's JNDI name plus the server name. Here, we add a connection factory for our CCI adapter whose JNDI name iseis/CciBlackBoxTxon the serverlocalhost.
- UNIX
j2eeadmin -addConnectorFactory \ eis/CciBlackBoxTx cciblackbox-tx.rar- Windows
j2eeadmin -addConnectorFactory eis/CciBlackBoxTx cciblackbox-tx.rar- Verify that the resource adapter has been deployed.
deploytool -listConnectors localhost
- The
deploytoolutility displays these lines:Installed connector(s): Connector Name: cciblackbox-tx.rar Installed connection factories: Connection Factory JNDI name: eis/CciBlackBoxTxSetting Up the Database
For Cloudscape, use the following procedure.
- Create the stored procedure.
- To compile the stored procedure, go to the
j2eetutorial/examplesdirectory and typeantprocs. This command will put theProcs.classfile in thej2eetutorial/examples/build/connector/procsdirectory.- Locate the
bin/userconfig.sh(UNIX) orbin\userconfig.bat(Windows) file in your J2EE SDK installation. Edit the file so that theJ2EE_CLASSPATHvariable points to the directory that contains theProcs.classfile.- Restart the Cloudscape server.
- Go to the
j2eetutorial/examplesdirectory and typeantcreate-procs-alias. This command creates aliases for the methods inProcs.class. Cloudscape uses method aliases to simulate stored procedures.- To create the
Coffeetable, go to the j2eetutorial/examplesdirectory and typeantcreate-coffee-table.For Oracle, use the following procedure.
- Start the database server.
- Run the
j2eetutorial/examples/sql/oracle.sqlscript, which creates both the stored procedures and theCoffeetable.Browsing the CoffeeApp Application
- In the GUI
deploytool, open thej2eetutorial/examples/ears/CoffeeApp.earfile.- Select the Resource Refs tab of the
CoffeeBeancomponent and note the following (Figure 17-3).Figure 17-3 Resource Refs Tab of the
CoffeeAppApplication
- The JNDI Name of
eis/CciBlackBoxTxmatches the name of the connection factory you added in step 2 of Deploying the Resource Adapter.- The User Name and Password fields contain dummy values (
XXX), since this EAR file was tested with a Cloudscape database. For other types of databases, you may be required to insert actual values in these fields. For these databases, you should also insert actual values in the Env. Entries tab ofCoffeeBean.
- Select the JNDI Names tab of
CoffeeApp(Figure 17-4). Note that the CCIEIS value in the Reference Name field has been mapped to theeis/CciBlackBoxTxvalue in the JNDI Name field.Figure 17-4 JNDI Tab of the
CoffeeAppApplicationDeploying and Running the CoffeeApp Application
- Deploy the application.
- In the GUI
deploytool, select ToolsDeploy.
- In the Introduction dialog box, select Return Client Jar.
- In a terminal window, go to the
j2eetutorial/examples/earsdirectory.- Set the
APPCPATHenvironment variable to the name of the stub client JAR file:CoffeeAppClient.jar.- Run the client.
runclient -client CoffeeApp.ear -name CoffeeClient -textauth- At the login prompts, enter
guestas the user name andguest123as the password.- The client should display the following lines:
Coffee count = 0 Inserting 3 coffee entries... Coffee count = 3
|
Home TOC Index |
|
Search
Feedback |