Oracle Application Server Containers for J2EE Enterprise JavaBeans Developer's Guide 10g (9.0.4) Part Number B10324-01 |
|
Active Components for Java (AC4J) is a framework that extends J2EE to enable applications to interact as peers in a loosely coupled manner. Two or more applications that are participating in a business interaction asynchronously exchange information for the purpose of requesting service and responding with results.
This chapter describes the Oracle solution for managing loosely coupled interactions between autonomous applications. It covers the following topics:
Often business applications require the ability to perform long-lived interactions between different application services. Applications should be able to communicate with other applications over a long period of time, without limiting resources, and with the ability to survive system crashes. Each application, when communicating with another application, exists as a peer. In other words, both applications can make requests to each other, but neither application can assume control over the resources that its peer application owns. In this environment, the communication between two applications is often disconnected, meaning the applications cannot rely on a constant system connection. The tasks that are performed sometimes may take days, even months, to complete; therefore, the long-lived interactions require asynchronous communication.
A practical example shows how this works. You might want to implement a purchase order (PO) processing system. The system would allow a client or customer to make an asynchronous purchase order request, without having to wait for the possibly rather lengthy purchase order processing to complete. The purchase order processing service itself would make two asynchronous requests, one to a credit service and one to an inventory service, to verify the customer's credit and check the inventory for the purchase order line items. The asynchronous nature of the two parallel requests to the services would allow the purchase order processing service to create an order based on the customer's request and respond with the corresponding order in a timely fashion. After both services returned with their responses, the purchase order processing service could consolidate both results and either cancel the order or continue processing it. (The returns would most likely not happen simultaneously and could potentially take hours, days, or even weeks.) The client could inquire on the status of the order at any given time, based on the order number originally returned by the purchase order processing service.
To implement such a system, the framework must enable applications to perform long-lived interactions as autonomous peers. For any application to exist over an unspecified period of time, the application must be reliable; it must be recoverable and restartable in case of system failures during that time. The application must also be scalable--that is, the long-running application cannot block execution or lock resources for long periods of time. For the application to execute within a reasonable time frame, the framework must provide performance enhancements through concurrently executing computations.
The Java 2 Platform, Enterprise Edition (J2EE), created by Sun Microsystems, provides an excellent environment for building reliable, scalable, tightly coupled applications. One of the main components that aid in this is Enterprise Java Beans (EJBs). It also supplies the building blocks for developing a new framework for satisfying the requirements for long-lived interactions, such as the one outlined in the previous example. EJBs, Java Message Service (JMS), and Java Transaction API (JTA) can be fused together in such a way that applications with support for long-lived interactions can easily be built.
Given the features of J2EE, why do we provide AC4J? Why are existing J2EE technologies, such as EJBs, JMS, and JTA not enough for building such a purchase order processing system?
Unfortunately, the tightly coupled synchronous communication of EJBs does not allow for long-lived interactions, or autonomous peer-to-peer communication. On the other hand, the loosely coupled, asynchronous communication of JMS does not allow the component-based request-response communication that is necessary between application services--at least not in a simple, out of the box manner. In addition, the need for the JTA coordinator to control all resources that are involved in the two-phase commit means that autonomous resources cannot be included in global transactions. Thus, J2EE by itself does not provide the full solution necessary for easily developing applications that use long-lived interactions.
We believe that to support the requirements for long-lived interactions, business solutions require an application component methodology that combines the advantages of EJBs, JMS, and JTA. Specifically, the new methodology must include answers to the following needs.
The application must be able to invoke EJB methods interactively, yet in the disconnected, non-blocking mode that is provided by asynchronous communication; in other words, a true integration of EJB method invocation with JMS messaging properties. This enables requests to be directed to the bean implementation, but in a loosely coupled manner that does not require static connections between the two parties, or the blocking of the requestor until a response is received. Furthermore, exceptions must be handled within the asynchronous environment and propagated back to the client. All services that are executing within the asynchronous environment should still contain a context for data, such as parameters and local variables. The environment should also provide basic time-management capabilities that allow for the programmatic or declarative delay of the execution flow, as well as for the forced execution of certain service operations based on time-out properties.
AC4J beans are known as active EJBs. These are EJBs (stateless session or entity beans) that extend JMS attributes. Thus, active EJBs have all of the properties, services, and Quality of Service of any EJB, plus the asynchronous abilities of JMS. An active EJB contains the business logic. These beans are loosely coupled beans, such that each bean can use either (or both) request-response synchronous or message-driven asynchronous communication to peer objects. Because active EJBs fully comply with the EJB specification, each active EJB uses all EJB features, such as being component-based and reusable and providing access to EJB services, such as security and transactional behavior.
All active EJBs exist within AC4J Interactions. Everything necessary for long-lived interactions is encapsulated within the AC4J Interaction, including all the request-response synchronous and one-way asynchronous communication properties. An AC4J Interaction is a long-lived unit of work that reflects the behavior of a business transaction. The AC4J Interaction replaces global transactions with its own methodology to avoid resource contention, allowing autonomous resources within the transaction, and enabling systems with differing constraints to interact. It groups a series of data exchanges between processes.
Underlying all of the AC4J interactions, the AC4J data bus routes AC4J data tokens (active data and events) between AC4J processes. The AC4J data bus is the fundamental component in AC4J. Applications attach to the AC4J data bus to exchange data and request services. The data bus is responsible for the routing and matching of AC4J data Tokens with registered AC4J reactions and enables transparent load-balancing of the attached application. AC4J data tokens describe a request for service, or a response from a service request, or an exception condition, such as an expiration of a timer.
The AC4J interaction can contain one or more AC4J processes. An AC4J process represents a business task. Each AC4J process provides the transactional and security context within which the application logic executes. It also manages concurrently executing computations, which are known as AC4J reactions. Furthermore, it encapsulates active data (local variables, input parameters, and responses) and matches the incoming active data to its intended recipients--AC4J reactions that are registered with the AC4J process. The AC4J process also maintains the data flow context that determines how to return the response to the caller. The context describes the destination to which the response data are returned.
An application can be dynamically partitioned into concurrently executing AC4J reactions. Each AC4J reaction is executed when specified conditions apply and all required data have arrived. AC4J reactions are the reactive entities that wait for the appropriate active data to arrive before invoking the intended bean method. After the method has processed the data, the AC4J reaction returns responses based on the context. All activities that are executed within the scope of the AC4J reaction execute under the ACID (atomicity, consistency, isolation, durability) properties of a JTA transaction.
AC4J reactions perform the detailed work of a business task by:
AC4J also furnishes certain time-management capabilities: allowing the execution flow to be delayed for a certain amount of time before executing a component, or executing a component after a certain amount of time (the time-out) even if the required data tokens for its execution have not yet arrived.
In summary, AC4J allows EJBs to interact in a loosely coupled fashion by performing the following:
All the preceding features are fully integrated in the EJB framework.
This section covers the following topics:
Traditional EJBs are passive--that is, they must be ready to immediately service a request from a client and return results quickly. Failure to do so causes an EJB to be unusable. AC4J allows standard stateless session and entity EJBs to become active. Active EJBs permit requests for service to be decoupled from the actual service execution. The policies that control when and which EJB methods are actually invoked are controlled by the service provider EJB. This decoupling permits service requests and service providers to interact as autonomous peers.
An application can create or look up a JEMHandle
and then request service from a business task, which is exposed in the EJB interface.
An active EJB is uniquely identified by a JEMHandle
object. A JEMHandle
object encapsulates the following:
An interaction is a long-lived unit of work that reflects the behavior of a business transaction. A business transaction can span multiple applications that reside in different organizations. The life of a business transaction differs from the life of a local or a global transaction in that the duration of a business transaction in such a disconnected environment can be arbitrarily long.
An interaction represents a business goal that you want to complete. For example, if a customer wants to buy something from a business, all the actions that are necessary to allow the customer to pay for and receive the item he wants are characterized as an interaction. The interaction groups a series of business data exchanges by providing the global execution context of the business transaction.
These applications can run in isolation and commit or roll back their own data without knowledge of other applications. However, these applications should not be considered as different pieces, because the relationships formed among them must be coordinated and their consistency maintained. When a business transaction becomes inconsistent, its participating applications may need to recover. The application recovery can be obtained by registering compensating reactions. For example, when the supplier has confirmed the purchase order request back to the buyer, the buyer must register a compensating reaction. The reaction monitors additional responses from the supplier that might inform him that, for example, the purchase order cannot be fulfilled because the manufacturing department is running late. If the supplier's confirmation of the request is cancelled, then the buyer's compensating reaction is matched and then fired to allow the buyer application to recover its application consistency. This reaction can pick a new supplier and request the item from the supplier or abandon the purchase order process completely.
An interaction is uniquely identified by an interaction identifier (IID). An interaction can contain multiple processes.
A process identifies a business task. In the purchase order example, a process exists for each of the following business tasks: creating a purchase order, checking inventory, and checking customer credit.
Each process does the following:
A process is uniquely identified by a JEMPortHandle
object, which encapsulates the process context and the JEMHandle
of the active EJB that the process belongs to. The process context is a union of an interaction identifier and the process activation identifier. AC4J automatically creates the interaction and process activation identifiers within a call operation. Alternatively, the application can supply them in the AC4J JEMSession::call
operation.
A reaction performs the detailed work of a process. Using this construct, an application can specify its persistence interest in the availability of a collection of correlated data tokens that trigger the execution of an active EJB method.
When a process is created as the result of an AC4J call operation, AC4J implicitly creates a base reaction. Additionally, an application can explicitly create a reaction at run time, using the JEMReaction::registerReaction
operation to synchronize on data tokens. The implicit or explicit registerReaction
operation specifies the active EJB method to be executed when matching succeeds.
The reaction processes incoming requests, returns results based on the request, and enforces business constraints to preserve application consistency. When all data tokens are available and the conditions are matched, the reaction is fired. It can consume one or more input data parameters, process them, and then possibly produces one or more output data tokens for other reactions. Results returned by a reaction (active EJB method) are converted to data tokens by the AC4J infrastructure and routed to the caller. The reaction can request additional services from other active EJBs to complete the business task. These requests result in the creation of new data tokens, which are pushed and routed by the AC4J data bus.
Reactions inside a process context instance can push data tokens to the AC4J data bus in the following ways:
JEMReaction::call
operations that request service from other processes in the same or different interaction context instance
JEMReaction::registerReactionTimer
operation
When the timer expires, AC4J pushes a time-out exception data token in the current reaction context instance.
Reactions inside a process context instance can pull data tokens from the AC4J data bus by registering one or more reactions in the current process context instance, using the JEMReaction::registerReaction
method.
One or more reactions can exist for each business task. A reaction is used for the request, and another for the response, to support the asynchronous nature in a request-response environment. The number of reactions depends on the number of requests and responses that are necessary.
"AC4J Example" demonstrates how you can receive an asynchronous communication between processes, but still have a request-response environment. The processOrder
process is the business task for creating the purchase order. To create the purchase order, you must check the inventory and the customer's credit. Thus, the processOrder
reaction invokes the following processes:
checkINV
When the customer asks for a new purchase and provides the data of the items wanted, the checkINV
process is activated, its JEMInventoryBean
active EJB is instantiated, and its base reaction (checkINV
) is fired. Later, it returns its results to the processOrder
process and its JEMPurchaseOrderBean
active EJB.
checkCRED
To check the customer's credit, this process is activated, its JEMCreditBean
active EJB is instantiated, and its base reaction (checkCRED
) reacts. Later, it returns its results to the takeOrder
process and its JEMPurchaseOrderBean
active EJB.
After sending the asynchronous requests to the checkINV
and checkCRED
processes, the processOrder
reaction registers another reaction in the same process (processOrderCallback
) that waits for the responses back from both the checkCRED
and checkINV
processes. When all data tokens expected from these processes are available, the processOrderCallback
reaction fires and processes the responses.
Note: To satisfy the AC4J requirement of not locking resources, the call should be an asynchronous AC4J call. However, you can still perform synchronous EJB calls to another bean. |
The activation of a reaction is triggered by the availability of data tokens. Availability is defined by the arrival of one or more data tokens, with the right conditions and the right access mode.
When an application requests a service by using an AC4J call operation, the system automatically pushes a request data token, which comprises the following:
takeOrder
)
JEMPortHandle
object of the service provider to which the request is destined
JEMPortHandle
object, which contains the process context (interaction and process activation identifiers) instance and the JEMHandle
of the requester process that will later receive the results from the service provider
Later, when a reaction returns a response data token that is automatically generated by AC4J when an active EJB returns or throws an exception, AC4J fills in the routing information. It sends the returned information to the caller process and fills the port handle object of the response data token. In the case in which the caller of the returning process is a client and not another process, then the data bus stores the response data token to a special data bus area from where the client can retrieve it, using the JEMSession::receiveReactionResponseObjectInstance
operation.
The data types of the objects that are carried inside an input or output data token can be basic data types (such as integer, string, float, boolean) or constructed class types (such as Java serializable objects).
Improving the autonomy, scalability, and availability of applications requires components that are requesting services to be unaware of the identity, location, and number of components that provide these services. In AC4J, applications are attached to a data bus before starting their operation. The AC4J data bus is responsible for routing and matching data tokens that are pushed and must be pulled by registered reactions. Additionally, the data bus enables scheduling, activation, and execution of the matched reactions.
The data bus routing subsystem is responsible for making the different types of data tokens available at the specified destination--the process context instance that comprises the interaction identifier and the process activation identifier--specified by a JEMPortHandle
object.
When data tokens are routed and become available in the data bus inside a process context instance, AC4J tries to match these data tokens with all registered reactions that are available in that context instance. The system tries to match the data token tags that are specified in a reaction template, evaluating all constraint conditions against the matched data tokens, to filter and discard the inappropriate ones.
Availability of some data tokens does not mean that a registered reaction matches immediately. Only when all data tokens required by a reaction become available does matching succeed.
For example, inside the processOrder
process, the processOrder
base reaction has registered the processOrderCallback
reaction that is waiting for the checkCRED
and checkINV
processes to respond. When the checkINV
process responds to the takeOrder
process, the processOrderCalback
reaction is not matched because it is also waiting for the checkCRED
process to respond. When the checkCRED
process responds to the processOrder
process, the processOrderCallback
reaction is matched.
Additionally, data tokens that are available in the data bus can be matched with a reaction that will be registered in the future. This matching can be used for sequencing processes, in which the completion of one process can enable another process.
Matching data tokens with reactions triggers the activation of zero, one, or more reactions, which are executed in parallel if they do not conflict for shared resources.
Each method of the remote interface of an active EJB implements the application business logic. When the data tokens become available and are matched with a reaction, AC4J verifies that the types (primitive or class types) of the data tokens that are matched on the tags also match the types of active EJB method types of the reaction. Then AC4J verifies that the matched reaction is authorized to pull the available matched data tokens. If everything passes successfully, then AC4J schedules the activation of the reaction.
When the matched reaction is fired, the AC4J container begins a JTA transaction and instantiates the requested active EJB (stateless session bean or entity EJB), using the primary key inside the JEMHandle
request object (the primary key is needed only in case of entity beans). Then the EJB method of the fired reaction is executed using the matched data tokens of the reaction.
AC4J automatically commits the current reaction at the end of every active EJB method. A reaction commit marks the end of a JTA transaction so that all its changes to shared data tokens, and all its service requests and responses that have been sent, become visible. The activation of a reaction has "exactly once" semantics (that is, the code specifies that it executes exactly once) if the reaction commits. If a failure occurs after a commit, then the reaction cannot be rolled back and the changes will persist. If a failure occurs before or during a commit, then the container rolls back the current reaction. A reaction rollback reverses all changes to shared data tokens, and the service requests and responses are never sent to any recipient component. In case of failures, the data bus tries again to fire the reaction for a preconfigured number of times. The reaction is marked as completed, with exception completion status if the maximum retry attempts are reached.
In traditional databases, where the duration of a transaction is short, abnormal situations cause the whole transaction to be undone, so all performed work is lost and must be submitted again for execution. Because interactions usually have long duration and contain a large number of reactions, AC4J provides additional mechanisms to handle exceptions.
AC4J automatically makes a reaction persist in the data bus if it completes successfully. The state that is saved (process input variable data, process local variable data, and data flow context information) can be used to continue the application with minimum restart time from the last reaction. When a node becomes unavailable, all reactions that were running and did not end successfully are rolled back. AC4J then again executes the interrupted reactions in another OC4J instance.
AC4J uses a mechanism to capture, propagate, and match the application state and control flow information that are needed for resuming an application after the unexpected error. Additionally, because reaction execution is data-driven, there is no need for the system to keep a volatile or persistent copy of the entire program state (such as program execution stack) to facilitate the storage of the control flow descriptors or the storage of data variables.
Figure 11-1 illustrates how, when all data tokens are available and the conditions are matched, the reaction fires, which causes the method to execute. This method can return results that are converted to data tokens by the AC4J infrastructure and routed to the caller, and can request additional services from other active EJBs to complete the business task. These requests result in the creation of new data tokens, which are pushed and routed by the AC4J data bus.
Before you can execute any AC4J applications, you must initialize an Oracle9i database as a repository for the AC4J data bus. You must set up the following in the database:
To initialize the AC4J data bus in the database, perform the following steps:
$AC4J_DEMO_DIR
to the path to the top-level directory of the AC4J demo (the directory that contains the files common.xml
, purchaseOrder
, and README.txt
). In UNIX, use this command:
setenv AC4J_DEMO_DIR path_to_AC4J_demo
J2EE_HOME
/sql/ac4j-sql.jar, into a new subdirectory AC4J_DEMO_DIR
/ac4j/sql:
cd $AC4J_DEMO_DIR/sql mkdir databus cd databus jar xvf $J2EE_HOME/sql/ac4j-sql.jar
createall.sql
that was unpacked in step 1, make sure that the arguments to the two calls in the script are correct for your database configuration. The two calls are:
createjem.sqlsys_user sys_pwd DB_instance tablespace
createclient.sqlsys_user sys_pwd AC4J_user AC4J_pwd tablespace
Table 11-1 contains the meanings and defaults of the arguments.
The argument values may be different for your installation. If so, change them by editing the script.
createall.sql
:
cd $AC4J_DEMO_DIR/sql/databus sqlplus /nolog @createall.sql
Configure the following data sources in the data-sources.xml
file in J2EE_HOME
/config/:
<data-sources> <!-- NON-Emulated DataSources: used for JEM server --> <data-source class="com.evermind.sql.OrionCMTDataSource" name="nonEmulatedDS" location="jdbc/nonEmulatedDS" connection-driver="oracle.jdbc.driver.OracleDriver" username="jemuser" password="jempasswd" url="jdbc:oracle:thin:@host
:port
:sid
" inactivity-timeout="30" > </data-source> <!-- Emulated DataSources: used for JEM client --> <data-source class="com.evermind.sql.DriverManagerDataSource" name="OracleDS" location="jdbc/OracleCoreDS" xa-location="jdbc/xa/OracleXADS" ejb-location="jdbc/OracleDS" connection-driver="oracle.jdbc.driver.OracleDriver" username="jemuser" password="jempasswd" url="jdbc:oracle:thin:@host
:port
:sid
" inactivity-timeout="30" > </data-source> </data-sources>
In the preceding code, you must replace host
, port
, and sid
with the host name, port number, and database SID of the Oracle database instance on which the AC4J data bus has been installed. Do the replacement for both data sources.
The jemuser
is the superuser user name, and the jemcliuser
is the default client user name, created with the SQL script createall.sql
.
AC4J is designed for complex applications that interact with each other over long periods of time. This section illustrates the usage of AC4J with a simple purchase order example. To simplify the presentation of the example, it does not show error handling and import statements. The example is packaged as one of the demos for the EJB functionality of OC4J 9.0.4. The demos are available for download from the OTN OC4J sample code site:
http://otn.oracle.com/tech/java/oc4j/demos/904
To run the example, perform the following steps:
$AC4J_DEMO_DIR
to the path to the top-level directory of the AC4J demo (the directory that contains the files common.xml
, purchaseOrder
, and README.txt
). In UNIX, use this command:
setenv AC4J_DEMO_DIR path_to_AC4J_demo
Create and populate the database tables required for running the example by executing the createTables.sql
script with the following commands:
cd $AC4J_DEMO_DIR/sql/ sqlplus /nolog @createTables.sql
You must set up the data sources used by the demo in the file J2EE_HOME
/config/data-sources.xml.
You must build all three services, by running the default ant
rule in each of their directories, as follows:
cd $AC4J_DEMO_DIR ant
Note:
The
Some of the sample applications that come with OC4J are set up to use |
You can also clean the services in a similar manner:
cd $AC4J_DEMO_DIR ant clean
cd $J2EE_HOME java -Doracle.aurora.jem.aq.close.interval=2 -jar oc4j.jar
Refer to the Oracle Application Server Containers for J2EE User's Guide for information on deploying an EJB on Oracle Application Server, and the Oracle Application Server Containers for J2EE Standalone User's Guide for a standalone implementation. The following example shows standalone usage:
cd $AC4J_DEMO_DIR java -jar $J2EE_HOME/admin.jar ormi://localhost admin welcome -deploy \ -file src/ejb/purchaseOrderService-ejb/lib/purchaseOrderService.ear \ -deploymentName purchaseOrderService java -jar $J2EE_HOME/admin.jar ormi://localhost admin welcome -deploy \ -file src/ejb/inventoryService-ejb/lib/inventoryService.ear \ -deploymentName inventoryService java -jar $J2EE_HOME/admin.jar ormi://localhost admin welcome -deploy \ -file src/ejb/creditService-ejb/lib/creditService.ear \ -deploymentName creditService
Three messages from the application server appear, one for each service that is deployed. Each message reads JEM Server started
.
Make a purchase order request by invoking the ant
rule reqpo
in the purchaseOrderService
subdirectory, as follows:
cd $AC4J_DEMO_DIR ant reqpo
The application server produces output similar to the following:
----------- sample of expected app server output from ant reqpo --------------- =======>PurchaseorderServiceBean.ejbCreate(): begin/end =======>PurchaseOrderServiceBean.takeOrder(): begin : clientName = scott : creditCardNumber = 1111-3333-4444-8888 : (productName, quantity) = pen, 3 : (productName, quantity) = pencil, 1 =======>PurchaseOrderServiceBean.createPO(): begin =======>PurchaseOrderBean.ejbCreate(): begin =======>PurchaseOrderBean.createPONumber(): begin : poNum=1 =======>PurchaseOrderBean.createPONumber(): end =======>PurchaseOrderBean.evaluateTotalCost(): begin =======>PurchaseOrderBean.evaluateTotalCost(): end---Total Cost = 0.75 =======>PurchaseOrderBean.ejbCreate(): end---poNumber= 1 =======>PurchaseOrderBean.ejbPostCreate(): begin =======>LineItemBean: ejbCreate: lineItemName = pen quantity = 3 =======>LineItemBean: ejbCreate: lineItemName = pencil quantity = 1 =======>PurchaseOrderBean.ejbPostCreate(): end---getLineItems().size= 2 createPO()---poNumber= 1 =======>PurchaseOrderServiceBean.createPO(): end : poNumber= 1 =======>PurchaseOrderServiceBean.startProcessingPOrder(): begin =======>PurchaseOrderServiceBean.startProcessingPOrder(): end =======>PurchaseOrderServiceBean.takeOrder(): end =======>PurchaseOrderServiceBean.processOrder(): begin---poNumber= 1 =======>PurchaseOrderServiceBean.callCreditService(): begin =======>PurchaseOrderServiceBean.callCreditService(): end =======>PurchaseOrderServiceBean.callInventoryService(): begin =======>PurchaseOrderServiceBean.callInventoryService(): end =======>PurchaseOrderServiceBean.registerAsyncRespHandler(): begin =======>PurchaseOrderServiceBean.registerAsyncRespHandler(): end =======>PurchaseOrderServiceBean.processOrder(): end---status= STATUS_INPROCESS =======>CreditServiceBean: ejbCreate: begin/end =======>CreditServiceBean: checkCRED: begin : clientName = SCOTT : creditCardNumber = 1111-3333-4444-8888 : amount = 0.75 =======>InventoryServiceBean: ejbCreate: begin/end =======>InventoryServiceBean: checkINV: begin : (product, quantity) = pen, 3 : (product, quantity) = pencil, 1 : CreditorRemote found : creditorName = SCOTT : availableCredit = 5000.0 =======>CreditServiceBean: checkCRED: end--returning CREDIT APPROVED : BEFORE: availableUnits[0] = 700 : AFTER: availableUnits[0] = 697 : BEFORE: availableUnits[1] = 600 : AFTER: availableUnits[1] = 599 : Returning: : invCheck[0]=true : invCheck[1]=true =======>InventoryServiceBean: checkINV: end =======>PurchaseOrderServiceBean.processOrderCallback(): begin.credInfo=CREDIT APPROVED =======>PurchaseOrderServiceBean.callBackMethod(): poNumber= 1 : invInfo[0] = true : invInfo[1] = true =======>PurchaseOrderServiceBean.callBackMethod(): end---credInfo=CREDIT APPROVED poNumber=1 currentStatus= STATUS_SHIPPED =======>PurchaseOrderServiceBean.getStatus(): begin---poNumber= 1 =======>PurchaseOrderServiceBean.getStatus(): end---poNumber= 1 status=STATUS_SHIPPED -------- (end) sample of expected app server output from ant reqpo ------------
The client produces output similar to the following:
--------------- sample of expected client output from ant reqpo -------------- reqpo: [java] Getting AC4J Connection and Session... [java] sendRequest: begin [java] customer name = IID = scott [java] credit card number = 1111-3333-4444-8888 [java] item names = pen,pencil [java] quantities = 3,1 [java] takeOrder request made. Process Context is: [java] Interaction Identifier (IID) = scott [java] Activation Identifier (AID) = AE3D71D56E835817E0340003BA137479 [java] Execute the following to receive PO response and track status: [java] ant -DAID=AE3D71D56E835817E0340003BA137479 resppo ------------- (end) sample of expected client output from ant reqpo ----------
The response to the purchase order request is collected by executing a second ant target, resppo
. You must supply the activity ID (AID) of the request as a property (ant -DAID=<AID> resppo
). As a convenience, reqpo
outputs the required ant
command line for that request, as the last line of its output. You can use cut-and-paste to copy the text to the command line, for quicker entry.
Here is an example that uses the preceding AID:
cd $AC4J_DEMO_DIR ant -DAID=AE3D71D56E835817E0340003BA137479 resppo
The program automatically terminates when it receives a STATUS_SHIPPED
status.
The exact output depends on how soon after the purchase order request is made that ant resppo
is executed.
Executing the resppo
client of ant
produces output similar to the following:
--------------- sample of expected output from ant resppo ------------------- resppo: [java] Getting AC4J Connection and Session... [java] receiveResponse: begin [java] receiveResponse: receiving async response [java] receiveResponse: response received [java] receiveResponse: Purchase Order number = 1 [java] receiveResponse: polling for PO status... [java] status = STATUS_SHIPPED [java] receiveResponse: Status = SHIPPED. Done. ------------ (end) sample of expected output from ant resppo ------------------
To rerun the client (and submit a different purchase order), rerun ant reqpo
, as described in step 6 under "Running the Example".
This section gives an overview of the steps that occur in the execution of the purchase order example. Figure 11-2 illustrates the steps, with the numbers corresponding to the step numbers in the description that follows.
The "Overview of Steps" is followed by a section for each step. Each step section explains the code for that step in detail.
In Figure 11-2, only the return of the purchase order number from the takeOrder
process to the client (steps 2 and 6) is shown as going through the AC4J data bus. This is a simplification, made to emphasize the flow of control between the different AC4J processes and reactions.
In fact, as "AC4J Data Tokens" explains, all AC4J calls (including replies) are mediated by the AC4J data bus. Figure 11-3 illustrates this for two of the steps from the example: the call from the processOrder
base reaction to the checkINV
process (step 3a) and the return of the reply of the checkINV
process to the takeOrder
process (step 4).
The steps that occur in the execution of the purchase order example are as follows:
The request (takeOrder
) is sent through the data bus and starts a new AC4J process.
The takeOrder
method does the following, to start the processing of the new purchase order:
processOrder
process. This is a method on the same bean (PurchaseOrderService
bean), but it forms the base reaction of a new AC4J process.
By initiating another reaction, the takeOrder
reaction can finish, and return the purchase order number assigned to this purchase order back to the client as early as possible, so the client can start to poll for the purchase order status.
The processOrder
reaction starts a new purchase order. It performs the following:
checkINV
process of the InventoryService
active EJB, to verify that the items are in inventory
checkCRED
process of the CreditService
active EJB, to verify that the customer's credit is satisfactory
processOrderCallback
reaction in the current process to receive the results from the preceding two requests.
Both the checkINV
and checkCRED
processes return responses.
The processOrderCallback
reaction, within the processOrder
process, reacts to the information that is provided by the checkINV
and checkCRED
processes. If satisfactory, the status of the purchase order entity bean is updated. No confirmation is sent to the client.
The purchase order number returned from step 2 is returned by the AC4J data bus and can be received asynchronously by the client at any time after that. After the client has the purchase order number, it then polls for the status of the purchase order, using a regular EJB call.
The code sample in Example 11-1 shows the steps that the client takes in sending an asynchronous request to the PurchaseOrderService
active EJB.
static final String DATA_SOURCE_NAME = "java:comp/env/jdbc/OracleDS"; static final String BEAN_NAME = "java:comp/env/ejb/PurchaseOrderService"; static final String ACTIVE_EJB_NAME = "JEMPurchaseOrderService"; static final String METHOD_NAME = "takeOrder"; static final String DB_USER = "JEMUSER"; static final String DB_PASSWD = "JEMPASSWD"; public static void main(String[] args) {// 1.0. Create a JNDI Context Context context = new InitialContext(); // 1.1. Look up the DataSource where AC4J data bus resides DataSource client_ds = (DataSource) context.lookup(DATA_SOURCE_NAME); // 1.2. Obtain a JDBC connection to the DataSource OracleConnection conn =(OracleConnection)client_ds.getConnection(DB_USER, DB_PASSWD);// 1.3. Create an AC4J connection, using the JDBC Connection ac4j_conn = new JEMConnection(conn); // 1.4. Create an AC4J session on the data bus ac4j_sess = new JEMSession(ac4j_conn); // 1.5. Look up the handle of the PurchaseOrderService Active EJB activeEJBHandle = (JEMHandle)context.lookup(ACTIVE_EJB_NAME); // 1.6. Prepare input parameters for asynchronous call // 1.6a. Make String and int arrays from itemNames and quantities args String[] itemNames = getStringArrayFromInput(args[3]); int[] quantities = getIntArrayFromInput(args[4]); // 1.6b. Make a Class array of input parameter types Class[] inputClassTypes = new Class[] { String.class,String.class, itemNames.getClass(), quantities.getClass() };// 1.6c. Make an Object array of input parameter values Object[] inputParams = new Object[] { (Object) new String(args[1]),(Object) args[2], (Object) itemNames, (Object) quantities };// 1.7. Make the asynchronous call // IID = customer name (args[1]), AID = null = auto-assigned JEMEmitToken request = ac4j_sess.call(args[1],null, activeEJBHandle, METHOD_NAME, inputClassTypes, inputParams, null, 0, 0);// 1.8. Commit the transaction ((OracleConnection)ac4j_sess.getJEMConnection().getConnection()).commit(); // 1.9. Get the AID auto-assigned to the request just made // Also, Confirm IID (will be value set above - just showing the API) // IID + AID = 'Process Context' of the request JEMPortHandle portHandle = request.getPortHandle(); String iid = portHandle.getIid(); String aid = portHandle.getAid(); System.out.println("takeOrder request made. Process Context is:"); System.out.println(" Interaction Identifier (IID) = " + iid); System.out.println(" Activation Identifier (AID) = " + aid); // 1.10 Close AC4J session and connection and JDBC connection ac4j_sess.close(); ac4j_conn.close(); conn.close();}
The client exists outside of an AC4J server and is requesting a service from an active EJB through the AC4J data bus. The AC4J data bus is the conduit and controls the asynchronous communication between the client and all reactions. Every client residing outside of an AC4J server must first connect to the AC4J data bus and create a new session for interaction to occur.
After a connection to the AC4J data bus has been retrieved and an AC4J session has been created within it, asynchronous messages can be sent to active EJBs in the same or other AC4J instances. The AC4J data bus coordinates the asynchronous messages and acts as a transactional manager for all active EJBs in the transaction.
The following explanation corresponds to the numbering in Example 11-1.
Because an AC4J connection exists above a JDBC connection, steps 1.0 to 1.4 retrieve that AC4J connection.
1.1 Retrieve the DataSource defined for the database acting as the AC4J conduit.
Define the DataSource
to use in the data-sources.xml
file as an emulated data source. See "Data Source Configuration" for more information.
Context context = new InitialContext(); DataSource client_ds = (DataSource) context.lookup(DATA_SOURCE_NAME);
1.2 Retrieve the JDBC connection from the DataSource object.
OracleConnection conn = (OracleConnection)client_ds.getConnection(DB_USER, DB_PASSWD);
1.3 Create an AC4J connection from the JDBC connection object.
ac4j_conn = new JEMConnection(conn);
1.4 Create an AC4J session in a specified data bus.
Providing the name of the data bus, use the AC4J connection to the database and create a session within the data bus in the indicated Oracle database.
ac4j_sess = new JEMSession(ac4j_conn);
1.5 to 1.11 Send an Asynchronous Request
Once an AC4J session has been created on the AC4J data bus, the client can send asynchronous messages to active EJBs. The client must provide the active EJB handle, the process handle, and all the required input parameters to the base reaction. Steps 1.5 to 1.11 explain the details of the call that the client must make to complete the AC4J request.
1.5 Obtain active EJB handle.
In a synchronous EJB environment, you would use a remote EJB handle to make an invocation. In an AC4J asynchronous environment, you must provide a similar handle of class type JEMHandle
that identifies an active EJB. You can obtain the active EJB handle by looking up the jem-name
that is defined in the orion-ejb-jar.xml
file (see "AC4J Active EJB Deployment").
// 1.5. Look up the handle of the PurchaseOrderService Active EJB activeEJBHandle = (JEMHandle)context.lookup(ACTIVE_EJB_NAME);
1.6 Prepare input parameters for asynchronous call.
The client prepares two arrays--an array of class types, to identify the type of each parameter, and an array of objects, to provide the values. Each has four parameters: two strings (customer name and credit card number), an array of strings (items), and an array of integers (quantities):
Class[] inputClassTypes = new Class[] { String.class, String.class, itemNames.getClass(), quantities.getClass() }; Object[] inputParams = new Object[] { (Object) new String(args[1]), (Object) args[2], (Object) itemNames, (Object) quantities };
1.7 Make the asynchronous call.
The JEMSession::call
contains the interaction identifier of the EJB, the process activation identifier to identify the process where the method is instantiated, and the JEMHandle
of the active EJB.
The interaction and process activation identifier (which together form the process context) are optional and can be omitted or can be null, in which case the system automatically creates them. The data bus identifies the context of the process and routes the data tokens to the intended process. Thus, all EJB calls are invoked asynchronously, through the mediation of the data bus.
// IID = customer name (args[1]), AID = null = auto-assigned JEMEmitToken request = ac4j_sess.call(args[1], null, activeEJBHandle, METHOD_NAME, inputClassTypes, inputParams, null, 0, 0);
1.8 Commit the transaction.
The client must commit the changes to the AC4J data bus. If the transaction is not committed, then the request is lost and is not visible to the AC4J data bus. To make the request visible to the AC4J data bus, perform the JDBC commit as follows:
((OracleConnection)ac4j_sess.getJEMConnection().getConnection()).commit();
1.9 Get the AID auto-assigned to the request just made.
In this case, the IID was given, but the AID was left null--to be auto-assigned by AC4J. The AID that is assigned must be read from the port handle, and retained to rendezvous asynchronously with the reply later, in Step 6.
JEMPortHandle portHandle = request.getPortHandle(); String aid = portHandle.getAid();
1.10 Close session and connections.
Finally, the client must close the AC4J session and connection, and the JDBC connection. This is necessary only because the client does not exist within an AC4J container. For applications running within an AC4J container (such as the active EJBs in this example), the container automatically closes the session and connections.
ac4j_sess.close(); ac4j_conn.close(); conn.close();
The code sample in Example 11-2 shows the steps that the purchase order service active EJB takes in processing the client's takeOrder
request.
After the client commits its request, the AC4J data bus matches the data tokens provided by the client with those of the requested reaction and internally schedules the instantiation of the PurchaseOrderBean
active EJB and activation of the takeOrder
process. The takeOrder
process starts a takeOrder
base reaction, which starts a new purchase order.
This reaction, takeOrder
, processes the client's request by performing the following steps:
purchase order
entity bean to represent the new purchase order. The creation of this bean, in turn, causes the appropriate product and line item beans to be created. This is done using regular EJB entity beans. AC4J is not involved.
processOrder
process. This is a method on the same bean (PurchaseOrderService
bean), but forms the base reaction of a new AC4J process.
purchase order
entity bean was created.
By initiating another reaction, the takeOrder
reaction can finish, and return the purchase order number that was assigned to this purchase order back to the client as early as possible, so the client can start to poll for the purchase order status.
Example 11-2 shows the code that performs these steps.
public int takeOrder(String clientName, String creditCardNumber,
String[] productNames, int[] quantities) throws RemoteException, TestException { int poNumber = 0; // 2.1. Create Purchase Order Entity Bean, and get PO Number in return // (Regular EJB Entity Beans code. Not shown) poNumber = createPO(clientName, creditCardNumber, productNames, quantities); // 2.2. Get the current AC4J reaction JEMReaction currentAC4JReaction = (JEMReaction)JEMReaction.getReaction(); // 2.3. Make an asynchronous call to the processOrder reaction on this // Bean, so can return PO Number to client now // 2.3a. Look up the AC4J handle for this Bean (PurchaseOrderService) Context context = new InitialContext(); JEMHandle ac4jPOSBeanHandle = (JEMHandle)context.lookup(jemPurchaseOrderServiceBeanName); // 2.3b. Prepare input parameter Types for processOrder method Class[] inputClassTypes = new Class[] { Integer.TYPE, productNames.getClass(), quantities.getClass() }; // 2.3c. Prepare input parameter Values for processOrder method Object[] inputParams = new Object[] { (Object) (new Integer(poNumber)), (Object) productNames, (Object) quantities }; // 2.3d. Make asynchronous call to processOrder JEMEmitToken emitToken = null; emitToken = currentAC4JReaction.call(null, null, ac4jPOSBeanHandle, "processOrder", inputClassTypes, null, inputParams, null, null, null, 0, 0); // 2.4 Return the PO Number of the new Purchase Order return poNumber; }
The AC4J data bus instantiates the active EJB, JEMPurchaseOrderBean (corresponding to the JEMHandle
that is provided by the client), in an AC4J server. The takeOrder
process starts a takeOrder
base reaction. The implementation of the takeOrder
method performs the steps shown in the example. The following explanation corresponds to the numbering in Example 11-2.
2.1 Create purchase order entity bean, and get purchase order number in return.
This step involves regular EJB entity bean programming, and is not shown.
2.2 Get the current AC4J reaction.
The current reaction, takeOrder
, is running in an AC4J server. The application code retrieves the current reaction as follows:
JEMReaction currentAC4JReaction = (JEMReaction)JEMReaction.getReaction();
This is done so that the call made in step 2.4 can be made in the context of the current request (that is, as another reaction in the same AC4J process).
The interaction ID (IID) and activity ID (AID) of the current process context could be obtained from the current reaction, as follows:
String iid = currentAC4JReaction.getIid(); String aid = currentAC4JReaction.getAid();
This would allow, among other things, the new call to be made in the context of the same interaction, but with a different activity ID, thus creating a new process in the same interaction.
2.3 Make an asynchronous call to the processOrder reaction on this bean.
The steps taken in making the asynchronous call are the same as those taken by the client in Steps 1.5 to 1.7. The transaction does not need to be committed by the application; AC4J automatically commits the transaction when the method returns.
By initiating another reaction, the takeOrder
reaction can finish, and return the purchase order number assigned to this purchase order back to the client as early as possible, so the client can start to poll for the purchase order status.
2.4 Return the purchase order number of the new purchase order.
The application returns the value using the normal synchronous style, as follows:
return poNumber;
How this value is passed back to the client depends on the style of call made by the client.
In this case, the client called the method asynchronously, by way the AC4J data bus. Therefore, AC4J takes the return value and packages it in an AC4J data token, which is placed on the data bus. The client receives the value asynchronously, by registering a reaction that consumes that data token. In this example, this is performed in Step 6. (Step 6 could proceed any time after step 2 completes, that is, once the reply data token is placed on the data bus.)
Had the client called the same active EJB method synchronously, by means of a regular EJB call, the value would have been returned to the client synchronously, in the normal way. The reason is that AC4J-enabled active EJBs can still be called as regular EJBs.
The processOrder
reaction starts a new purchase order. It performs the following:
checkINV
process of the InventoryService
active EJB, to verify that the items are in inventory.
checkCRED
process of the CreditService
active EJB, to verify that the customer's credit is satisfactory.
processOrderCallback
reaction in the current process to receive the results from the preceding two requests.
Example 11-3 shows the code that performs these steps.
public void processOrder(int poNumber, String[] productNames, int[] quantities) throws RemoteException, TestException { PurchaseOrderRemote poRemote = null; // 3.1. Get the current AC4J reaction JEMReaction currentAC4JReaction = (JEMReaction)JEMReaction.getReaction(); // 3.2. Look up purchase order Entity Bean, so can pass reference to // Credit and Inventory Services poRemote = lookupPOBean(new Integer(poNumber)); // 3.3. Make an asynchronous call to the Credit Service JEMEmitToken creditToken = callCreditService(currentAC4JReaction, poRemote); // 3.4. Make an asynchronous call to the Inventory Service JEMEmitToken inventoryToken = callInventoryService(currentAC4JReaction, productNames, quantities); // 3.5. Register processOrderCallback Reaction registerCallback(currentAC4JReaction, new JEMEmitToken[] {inventoryToken, creditToken}); // 3.6. Update Purchase Order status to INPROCESS poRemote.setStatus(Status.getString(Status.STATUS_INPROCESS)); }
The following explanation corresponds to the numbering in Example 11-3.
3.1 Get the current AC4J reaction.
The purpose of this is to register the new (callback) reaction in 3.6. The callback reaction is registered in the same process as the current reaction so that it can access data that is associated with this process. See Step 5 for details.
JEMReaction currentAC4JReaction = (JEMReaction)JEMReaction.getReaction();
3.2 Look up purchase order entity bean.
This is done so that the reference to the purchase order entity bean can be passed in the request made to the credit service, so that it can query the bean directly.
poRemote = lookupPOBean(new Integer(poNumber));
3.3 Make an asynchronous call to the credit service.
The steps taken in making the asynchronous call are identical to those taken by the client in Steps 1.5 to 1.7. The transaction does not need to be committed by the application; AC4J automatically commits the transaction when the method returns. The checkCRED
method of the credit service active EJB is called, starting a new checkCRED
process.
JEMEmitToken creditToken = callCreditService(currentAC4JReaction, poRemote);
3.4 Make an asynchronous call to the inventory service.
This step is similar to 3.3. The only difference is that the purchase order entity bean reference is not passed to the service. The product names and quantities are sufficient information. This difference is not an AC4J issue; rather, it is how this example has been implemented.
The checkINV
method of the credit service active EJB is called, starting a new checkINV
process.
JEMEmitToken inventoryToken = callInventoryService(currentAC4JReaction, productNames, quantities);
3.5 Register processOrderCallback reaction.
Register a new reaction in the current process:
registerCallback(currentAC4JReaction, new JEMEmitToken[] {inventoryToken, creditToken});
The reaction specifies interest in the tokens returned by the asynchronous calls to the credit and inventory services, made in steps 3.3 and 3.4. The new reaction will be scheduled to execute when the reply tokens from both calls are placed on the AC4J data bus (that is, when both services return from the calls).
The callback reaction is registered in the same process as the current reaction so that it can access data that is associated with this process. See "Step 5: Asynchronous Responses Are Processed by the processOrderCallback Reaction" for details.
3.6 Update purchase order status to INPROCESS.
This state is maintained so that it can be returned to a client that polls to check the status of the purchase order. See "Step 6: Client Receives Purchase Order Number Asynchronously and Polls for Purchase Order Status" for details.
poRemote.setStatus(Status.getString(Status.STATUS_INPROCESS));
The takeOrder
base reaction is completed only after the AC4J infrastructure commits the transaction that includes the calls to the other two active EJBs and a registered reaction.
The checkINV
and checkCRED
processes receive the requests from the AC4J data bus as if they were invoked from any other EJB. The JEMInventoryBean and JEMCreditBean active EJBs are instantiated. The checkINV
and checkCRED
base reactions are fired when they receive the data tokens, which were initiated from the processOrder
reaction, from the AC4J data bus. Both of them receive the request, perform their tasks, and return. The returned values are forwarded by the AC4J data bus to the registered reaction, processOrderCallback
.
The code sample in Example 11-4 illustrates the checkINV
method. The checkCRED
method is the same as in its AC4J responsibilities.
public boolean[] checkINV(String[] productNames, int[] quantities) throws RemoteException, TestException {boolean[] invCheck = new boolean[productNames.length]; // The business logic is not shown ... return invCheck;}
The application returns the value using the normal synchronous style, as follows:
return invCheck;
But the value is returned asynchronously, by a data token placed on the data bus. See step 2.4 for details.
Both checkINV
and checkCRED
processes return their responses to the processOrderCallback
reaction through the AC4J data bus. The AC4J data bus ensures that the return data-tokens have valid processOrder
process context and match the input parameter types of the processOrderCallback
reaction. When both parameters arrive, the processOrcerCallback
reaction fires and executes the processOrderCallback
method of the JEMPurchaseOrderBean active EJB.
The processOrderCallback
reaction reacts to the information provided by the checkINV
and checkCRED
processes and updates the state of the purchase order entity bean accordingly. Note that no confirmation is sent to the client, which polls for the status of the order--as "Step 6: Client Receives Purchase Order Number Asynchronously and Polls for Purchase Order Status" describes.
The code sample in Example 11-5 shows the processOrderCallback
method.
public void processOrderCallback(boolean[] invInfo, String credInfo) throws RemoteException, TestException {PurchaseOrderRemote poRemote = null; Integer poNumber = null; // This Reaction (processOrderCallback) was invoked asynchronously by // it's parent Reaction (processOrder) by doing // "JEMReaction.registerReaction". // // This reaction can access all it's parent's variables by using // the JEMProcess concept as shown in the following 3 steps. // // In this case, it is the PO Number that is accessed. // 5.1. Obtain the current AC4J process handle JEMProcess currentAC4JProcess = (JEMProcess) JEMProcess.getProcess(); // 5.2. Retrieve the first input parameter of parent reaction (processOrder) JEMTuple inTuple = currentAC4JProcess.getInTupleByIndx(0); // 5.3. Get PO Number from parent's input parameter poNumber = (Integer)inTuple.getObjInst(); // 5.4. Look up PurchaseOrder Entity Bean poRemote = lookupPOBean(poNumber); // 5.5. Start updating the PO status depending on the replies... poRemote.setStatus(Status.getString(Status.STATUS_PROCESSED)); // 5.6. Check the credit info int local_status = Status.STATUS_PROCESSED; if(credInfo != null) {if(credInfo.equalsIgnoreCase("credit approved"))local_status = Status.STATUS_VALID_CREDIT;else if(credInfo.equalsIgnoreCase("credit failed"))local_status = Status.STATUS_CANCELLED_NOCREDIT;else if(credInfo.equalsIgnoreCase("Invalid Credit Card"))local_status = Status.STATUS_INVALID_CREDIT_CARD;} elselocal_status = Status.STATUS_CANCELLED_NOCREDIT;// 5.7. Check the inventory info for(int i=0; local_status == Status.STATUS_VALID_CREDIT && i<invInfo.length;i++){if(!invInfo[i])local_status = Status.STATUS_CANCELLED_NOINV;} // 5.8. Set the final status of the Purchase Order if(local_status == Status.STATUS_VALID_CREDIT)poRemote.setStatus(Status.getString(Status.STATUS_SHIPPED));elsepoRemote.setStatus(Status.getString(local_status));}
The following explanation corresponds to the numbering in Example 11-5.
5.1 Obtain the current AC4J process handle.
The processOrderCallback
reaction resides in the same process as the processOrder
reaction that created it; processOrder
is the base reaction of the processOrder
process, and processOrderCallback
is a child reaction.
A handle to the process is obtained so that the current reaction can access data that is global to the process - in this case one of the input parameters to the base reaction, the purchase order number.
JEMProcess currentAC4JProcess = (JEMProcess) JEMProcess.getProcess();
5.2 Retrieve the first input parameter of parent reaction.
The parent reaction is the base reaction of this process. The first parameter is the first token in the tuple:
JEMTuple inTuple = currentAC4JProcess.getInTupleByIndx(0);
5.3 Get purchase order number from parent's input parameter.
From the whole set of parameters, the purchase order number is retrieved:
poNumber = (Integer)inTuple.getObjInst();
5.4 Look up purchase order entity bean.
This entity bean is obtained to be able to update the order status steps 5.5 to 5.7:
poRemote = lookupPOBean(poNumber);
5.5 to 5.7 Update the purchase order status, depending on the replies.
These steps are normal entity bean operations, not involving AC4J.
The client must know the response to its purchase order request. As stated earlier, each request (or call) is identified by a process context (interaction ID, IID, plus activation ID, AID). Using the process context, the client can pull the response from the AC4J data bus.
The client can then parse the received JEMEmitToken from the response. If the client existed inside the OC4J container, then the container would deconstruct the JEMEmitToken to the required type. Outside the container, the client must parse out the response correctly, as shown in Example 11-6.
Only the purchase order number is returned by the asynchronous reply, not the status of the order itself. After the client has the order number, it polls the purchase order entity bean directly, by means of regular synchronous EJB calls, until the order completes.
static final String DATA_SOURCE_NAME = "java:comp/env/jdbc/OracleDS"; static final String BEAN_NAME = "java:comp/env/ejb/PurchaseOrderService"; static final String ACTIVE_EJB_NAME = "JEMPurchaseOrderService"; static final String DB_USER = "JEMUSER"; static final String DB_PASSWD = "JEMPASSWD"; public static void main(String[] args) throws ClassNotFoundException, Exception {// 6.0. Create a JNDI Context Context context = new InitialContext(); // 6.1. Look up the DataSource where AC4J data bus resides DataSource client_ds = (DataSource) context.lookup(DATA_SOURCE_NAME); // 6.2. Obtain a JDBC connection to the DataSource OracleConnection conn = (OracleConnection)client_ds.getConnection(DB_USER, DB_PASSWD); // 6.3. Create an AC4J connection, using the JDBC Connection ac4j_conn = new JEMConnection(conn); // 6.4. Create an AC4J session on the data bus ac4j_sess = new JEMSession(ac4j_conn); // 6.5. Look up the handle of the PurchaseOrderService Active EJB activeEJBHandle = (JEMHandle)context.lookup(ACTIVE_EJB_NAME); // 6.6. Receive Response for the specified Process Context // (IID + AID) plus reaction (takeOrder) // Blocks indefintely, because timeout is 0 JEMEmitToken resptoken =ac4j_sess.receiveReactionResponse(args[1], // IIDargs[2], // AID activeEJBHandle, // Active EJB Handle "takeOrder", // Reaction/Method 0); // Timeout (seconds)// 6.7. Retrieve data from Reaction Response Object object = resptoken.getReactionResponseObjectInstance(); // 6.8. Extract PurchaseOrder Number from data Integer poNumber = retrievePONumber(object); // 6.9. Commit the transaction ((OracleConnection)ac4j_sess.getJEMConnection().getConnection()).commit(); // 6.10. Look up the PurchaseOrder Service Bean PurchaseOrderServiceRemote posb = lookupPurchaseOrderServiceBean(); // 6.11. Poll the Purchase Order status - synchronously (regular EJB calls) // until the status = shipped System.out.println("receiveResponse: polling for PO status..."); String status = ""; while(status.compareTo("STATUS_SHIPPED") != 0) {status = posb.getStatus(poNumber.intValue()); System.out.println(" status = " + status);} if (status.compareTo("STATUS_SHIPPED") == 0) {System.out.println("\nreceiveResponse: Status = SHIPPED. Done.");}}
The following explanation corresponds to the numbering in Example 11-6.
6.0 to 6.4 Retrieve an AC4J connection.
These steps are identical to steps 1.0 to 1.4.
6.5 Look up the handle of the purchase order service active EJB.
activeEJBHandle = (JEMHandle)context.lookup(ACTIVE_EJB_NAME);
This handle must be provided to the call in step 6.6.
6.6 Receive response for the specified process context:
JEMEmitToken resptoken = ac4j_sess.receiveReactionResponse(args[1], // IID args[2], // AID activeEJBHandle, // Active EJB Handle "takeOrder", // Reaction/Method 0); // Timeout (seconds)
The IID and AID were output at the end of "Step 1: Client Sends an Asynchronous Request to the Purchase Order Service Active EJB", and are passed in to "Step 6: Client Receives Purchase Order Number Asynchronously and Polls for Purchase Order Status".
6.7 Retrieve data from reaction response.
The return value is extracted from the returned token, as a Java object.
Object object = resptoken.getReactionResponseObjectInstance();
6.8 Extract purchase order number from data.
The return value is then cast to its actual type.
Integer poNumber = retrievePONumber(object);
6.9 Commit the transaction.
This step is necessary because the client is outside of the AC4J container.
((OracleConnection)ac4j_sess.getJEMConnection().getConnection()).commit();
6.10 and 6.11 Look up the purchase order service bean, then poll its status until shipped.
After the purchase order number has been obtained, the client polls the purchase order entity bean directly to get the order status. This process continues until the status changes to shipped. This is regular EJB code and is not shown here.
The active EJB is developed as any other EJB. The changes that enable the EJB to be used in an AC4J interaction are in the OC4J-specific deployment descriptor. These are discussed below:
Deploy the EJB with AC4J element specifications in the OC4J-specific deployment descriptor. The following example defines the takeOrder
EJB as an active EJB.
<jem-server-extension>
element defines the database with the data bus that the active EJBs in this JAR file use for their AC4J communication.
<jem-server-extension data-source-location="jdbc/jemSuperuserDS"> <description>AC4J datasource location</description> </jem-server-extension>
<jem-deployment>
element in the orion-ejb-jar.xml
file identifies the EJB that is defined in the ejb-jar.xml
file as an active EJB. This element provides an AC4J name (jem-name) that is used to identify the bean within the AC4J calls. For example, this bean is defined as JEMPurchaseOrderBean
, which was used in the JEMHandle
creation. The identity of the caller, which is allowed to request services and retrieve responses from the active EJB, can be declared in the called-by tag. This caller tag identifies the user in the data bus. For example, JEMCLIUSER is the user name that was employed to create a jem-session
,
<jem-deployment jem-name="JEMPurchaseOrderBean" ejb-name="PurchaseOrderBean"> <description>AC4J EJB</description> <called-by> <caller caller-identity="JEMCLIUSER"/> </called-by> </jem-deployment>
Here is the entire orion-ejb-jar.xml
file for the three active EJBs:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE orion-ejb-jar PUBLIC "-//Evermind//DTD Enterprise JavaBeans 1.1 runtime//EN" "http://xmlns.oracle.com/ias/dtds/orion-ejb-jar.dtd"> <orion-ejb-jar> <enterprise-beans> <entity-deployment name="Products" location="ejb/Product" table="PRODUCTS_PURCHASEORDERSERVICE" data-source="jdbc/nonEmulatedDS" > </entity-deployment> <jem-server-extension data-source-location="jdbc/nonEmulatedDS" scheduling-threads="1"> <description>JEMServer Deployment</description> </jem-server-extension> <jem-deployment jem-name="JEMPurchaseOrderService" ejb-name="PurchaseOrderService" > <called-by> <caller caller-identity="JEMUSER" /> </called-by> <security-identity> <description>using the caller identity</description> <use-caller-identity /> </security-identity> </jem-deployment> </enterprise-beans> <assembly-descriptor> <security-role-mapping name="JEMUSER"> <user name="JEMUSER" /> </security-role-mapping> <default-method-access> <security-role-mapping name="<default-ejb-caller-role>" impliesAll="true" /> </default-method-access> </assembly-descriptor> </orion-ejb-jar>
|
![]() Copyright © 2002, 2003 Oracle Corporation. All Rights Reserved. |
|