Oracle9iAS Containers for J2EE Enterprise JavaBeans Developer's Guide and Reference Release 2 (9.0.2) Part Number A95881-01 |
|
Active Components for Java (AC4J) enables applications to interact as peers in a loosely-coupled manner. Two or more applications participating in a business interaction, exchange information for requesting service and for responding results.
This document describes the architecture needed and the software provided to manage loosely-coupled, interactions between autonomous applications.
The future of business applications requires the ability to perform loosely-coupled interactions. That is, applications should be able to exchange information with other applications over a long period of time, without limiting resources, and by surviving system crashes. The following lists the requirements for loosely-coupled interactions:
The current architectures available are the following:
The following sections briefly describe these models and show why they do not provide the basis necessary for the five goals presented in "Future Needs of Business Applications".
The Remote Procedure Call (RPC) programming model facilitates a tightly-coupled environment that provides for request/response communication. Transactional RPC implementations provide for ACID qualities.
Most RPC implementations currently provide two modes of method invocations: synchronous and deferred synchronous.
The client program blocks when a remote invocation is made and waits until the results arrive or an exception is thrown. Examples of application types that use transactional RPC implementations are EJB, and most CORBA applications. Web services are also based on the RPC model, but are not transactional.
This model of communication--also called on-line or connected--is based on the request/response paradigm, where the requester and responder of the service are tightly-coupled. Tightly-coupled applications understand how to reply transparently to the requestor.
The programs must be available and running for the application to work. In the event of a network or machine failure, or when the application providing the service is busy, the application is not able to continue forward with its processing work. In this case, the state is inconsistent and the application must decide to rollback to a consistent state through JTA. Also, it is not autonomous. One application can control resources of other applications for a long time.
JTA is based on the two-phase commit specification. Two phase commit protocol may cause loss of application autonomy in the case of network disconnection, where the coordinator is unable of making a coherent global decision over the outcome of the global transaction for a long period of time.
If a purchase order is created and the customer wants to purchase 20 widgets, then the transactional RPC application must do two things:
In this example, an RPC synchronous application would (within a global transaction) do the following:
If all request come back with a satisfactory report, then the transaction is committed and the purchase order is forwarded on to shipping. If one of the two requests fails, the transaction is rolled back. Granted, the application could have the following alternatives that prevents the transaction from being rolled back:
If the transaction is rolled back, the purchase order is voided.
An RPC deferred synchronous invocation is queue-oriented. The client places a request in a queue and is then able to continue processing without blocking for the response. An example of this is a CORBA DII application.
The client does not need to wait for a reply to the request. Instead, it continues processing. Then when the client wants to receive a response, it blocks or polls for the availability of the response. A response can only be delivered to the same process that made the original deferred request. Thus, if multiple deferred requests are pending, only one response is processed at a time.
If the client is non-existent, then the response is lost. Thus, for deferred execution to work correctly in the presence of network, machine, and application failures, the requests must be stored persistently and processed exactly once.
In the purchase order example, the requests to the inventory and credit bureau can be made in parallel. After executing both requests, the client can poll for both responses. The disadvantages would be the same as listed within the RPC synchronous invocation example.
The database transactional queuing model supports a loosely-coupled environment where applications use one-way communication. Oracle AQ is an implementation of a database transactional queuing model.
Applications need to process and deliver each message exactly once, even in presence of multiple failures of the sender or the receiver. Mixing the transactional ACID construct with queue processing creates a model that enables applications to reliably process messages with the ACID guarantees.
Applications can be disconnected for long periods of time and occasionally they can reconnect to communicate, using messages. By de-coupling the applications that send messages from the applications that receive messages and process them, queuing facilitates complex scheduling of autonomous applications. Each message can be durably saved until processed exactly once. Processing of the data is performed in a time independent fashion, even in a situation where a message receiver is temporary unavailable.
Delivers and processes messages exactly once, no matter whether the network or receiver application is available or not.
This model is based on sending and receiving messages. It is not based on requesting and responding to service requests, which is the foundation of all business protocols for loosely-coupled applications. To satisfy this requirement, the application shoulders the burden of creating and parsing each message. Both sides must know the format, security, and headers required for each message. There is no automatic mechanism for routing messages and executing business methods. The implementation of application logic for these mechanisms is the responsibility of the applications. If a response is called for, the application cannot easily reply, because there is no context that captures the relationship between a requester and a responder application, which is the case for RPC. It is not intended for a request/response environment, so if the client needs a response back from the destination object, it must receive and parse a separate message off of its own queue.
Exception handling describes communication failures and not application exceptions.
There is no guarantee for the consistency of the business transactions. Instead, the program itself must guarantee that the application semantic rollbacks occur appropriately in a failure situation.
In the purchase order example, the client would enqueue a message to the inventory queue and another to the credit bureau queue. Both must be reliably processed once in order for the transaction to commit. If either the inventory is not available or the client's credit is not good, the business transaction cannot be successfully completed and another message must be created to semantically rollback the one message that was processed positively.
The RPC and transactional database queuing models both have advantages and disadvantages. The disadvantages within J2EE application types are as follows:
The disadvantages prevent each model from solving the business goals laid out in "Future Needs of Business Applications". Thus, a new model is necessary to incorporate the advantages of both models and exclude the disadvantages.
AC4J is a manager of loosely-coupled interactions between autonomous EJB applications. You can partition the application into concurrently executing active units of work--known as Reactions--whose execution is driven by data availability and its purpose is to execute business logic and produce new data. AC4J coordinates the flow of data between Reactions. When data become available on AC4J, the conditions specified by all registered Reactions are checked and if satisfied, then the execution of the methods of all matched Reactions is triggered.
AC4J allows EJBs to interact in a loosely-coupled fashion. It provides the following features:
It hides queues/topics and related JMS constructs from applications, provides automatic definition of communication message formats, and packs/unpacks messages, automatic routing of service requests to the appropriate service provider, automatic security context propagation, authorization and identity impersonation, and automatic exception routing and handling, which is integrated in the EJB framework.
Composite matching on available data based on specified rules, which describe under which conditions these data can fire which EJB method. Transparent scheduling and activation of EJBs and execution of their methods.
AC4J provides a framework for loosely-coupled interactions, which are included in the following components:
Figure 8-1 demonstrates the relationship of these components to each other. The sections following describe each component.
An EJB provides a natural way for describing business object--such as a customer, a purchase order, or an invoice. The externally visible business tasks of a business object, which is accessible by other applications, is separated from their internal implementation details and are described in the EJB interface.
Traditional EJBs are passive, they must be ready to immediately service a request from a client and return results quickly. Failure to deliver on these promises 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 de-coupled from the actual service execution. The policies that control when and which EJB method(s) are actually invoked are controlled by the service provider EJB. This de-coupling permits service request and service providers to interact as autonomous peers.
An application can create or lookup 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 Active EJB name, the J2EE application name, the EJB JAR name, the EJB name, the EJB bean class name, the EJB home interface name, the EJB remote interface name, the instance name (SID) of the database in which the Databus resides on, and the primary key of the EJB that is available only for entity beans.
An Interaction is a long-lived unit of work that reflects the behavior of a business transaction. A business transaction may span multiple applications that reside in different organizations. Contrary to the life of a local or a global transaction, the duration of these business transactions in this disconnected environment, can be long.
The Interaction represents a business goal that you want to complete. For example, if a customer wants to buy something from a business, the entire actions necessary to allow the customer to pay for and receive the item he/she wants is 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 may run in isolation and commit/rollback their own data without knowledge of other applications. However these applications should not be considered as different pieces, because the relationships formed amongst 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 Reaction(s). For example, once the supplier has confirmed the purchase order request back to the buyer, the buyer needs to register a compensating reaction that monitors additional responses from the supplier that may inform him that the purchase order cannot be fulfilled because the manufacturing department is running late. If the supplier's promise 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 him 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 our purchase order example, a Process would exist for each of the following business tasks: creating a purchase order, checking inventory, checking customer credit, and shipping the order.
Each Process does the following:
Figure 8-2 demonstrates an Active EJB, Interaction, and two of its Processes.
A Process is uniquely identified by a JEMPortHandle
object. A JEMPortHandle
object encapsulates the Process Context and the JEMHandle
of the Active EJB that the Process belongs to. The Process Context is an 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 on the availability of a collection of correlated data tokens which trigger the execution of an Active EJB method. A Reaction is a combination of the following:
In every Process, there is a base Reaction, which is implicitly created by AC4J when a Process is created as a result of a AC4J call
operation. 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 Reaction template and the Active EJB method to be executed when matching succeeds.
Reactions (EJB methods) can access/modify shared database objects. These objects can be traditional database objects; thus, facilitating coarse grain information sharing in a transactional manner. Similarly, the Reactions exchange fine grain information--such as Active EJB method input parameters and return values--using the AC4J Databus.
The Reaction processes incoming requests, can return results based on the request, and enforces business constraints to preserve application consistency. When a Reaction is fired, it can consume one or more input data parameters, process them, and then possibly produce one or more output data tokens for other Reactions. Figure 8-3 demonstrates how when all data tokens are available and the conditions are matched, the Reaction fires, which causes the method to execute. This method may return results which are converted to data tokens by AC4J infrastructure and routed to the caller. This method may request additional services from other Active EJBs to complete the business task. These requests result in the creation of new data tokens that are pushed and routed by the AC4J Databus.
Reactions inside a Process Context instance can push data tokens to the AC4J Databus 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 Databus by registering one or more Reactions in the current Process Context instance using 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 necessary.
The following example demonstrates how one can receive an asynchronous communication between processes, but still have a request/response environment. The takeOrder
Process is the business task for creating the purchase order. In order to create the purchase order, you must check the inventory and the customer's credit. Thus, the takeOrder
Reaction invokes the following Processes:
checkINV
--Under the conditions that the customer asks for a new purchase and provides the data of the items wanted, the checkINV
Process is activated and its JEMInventoryBean Active EJB is instantiated and its base Reaction, checkINV
, reacts. Later, it returns its results to the takeOrder
Process and its JEMPurchaseOrderBean Active EJB.
checkCRED
--This Process is activated and its JEMCreditBean Active EJB is instantiated and its base Reaction, checkCRED
, reacts to check the customer's credit. 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 takeOrder
Reaction registers another Reaction in the same Process--procPO
--that waits for the responses back from both the checkCRED
and checkINV
Processes. Once all data tokens expected from these Processes are available, the procPO
Reaction fires and processes the responses. As shown in Figure 8-4, both the takeOrder
and procPO
Reactions exist in the same process, as they are components of the same request/response communication.
Note: In order 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 is requesting a service by using an AC4J call operation the system automatically pushes a request data token, which consists of the following:
takeOrder
)
JEMPortHandle
object of the service provider to whom 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 needed for sending the returned information to the caller Process and fills the port handle object of the response data token. In the case where the caller of the returning Process is a client and not another Process, then the Databus stores the response data token to a special Databus area from where the client can retrieve it using the JEMSession::receiveReactionResponseObjectInstance
operation.
The data types of the objects carried inside an in or out data tokens 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 requesting services to be unaware of the identity, location, and the number of components that provide these services. In AC4J, applications are attached to a Databus before starting their operation. The AC4J Databus is responsible for routing and matching of data tokens that are pushed and need to be pulled by registered Reactions. Additionally, it enables scheduling, activation, and execution of the matched Reactions.
The Databus routing subsystem is responsible for making the different types of data tokens available at the specified destination, Process Context instance that is a union of { Interaction identifier and Process Activation identifier }, specified by a JEMPortHandle
object.
When data tokens are routed and become available in the Databus inside a Process Context instance, AC4J tries to match these data tokens with all registered Reaction(s) available in that context instance. The system tries to match the data token tags 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 token(s) does not mean that a registered Reaction will match immediately. It is only when all data tokens, required by a Reaction, become available that matching succeeds. For example, inside takeOrder
Process the takeOrder
base Reaction has registered the procPO
Reaction that is the waiting for the checkCRED
and checkINV
Processes to respond. When checkINV
Process responds to the takeOrder
Process, procPO
Reaction is not matched because it is also waiting for the checkCRED
Process to respond. It is when the checkCRED
Process responds to the takeOrder
Process that the procPO
Reaction is matched.
Additionally, data token(s) available in the Databus, may be matched with a Reaction that will be registered in the future. This can be used for sequencing Processes, where the completion of one Process can enable another Process. Inside the same Interaction, the takeOrder
Process must be completed before cancelOrder
Process can start executing. If takeOrder
Process has not completed but cancelOrder
Process is requested from a client, its base Reaction, which is implicitly created by the system, will not be matched because it is waiting for the completion data token of the takeOrder
Process to be available. If takeOrder
Process has completed (already pushed its completion data token), then cancelOrder
Process is requested from a client and it will be immediately matched, because the completion data token of the takeOrder
Process is already available.
Matching data tokens with Reactions triggers the activation of zero, one, or more Reactions, which are executed in parallel if they don't 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 matched with a Reaction, AC4J verifies that the types (primitive or class types) of the data tokens matched on the tags, also match the types of the Reaction Active EJB method types. 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. Then the EJB method, of the fired Reaction, gets 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/responses that have been sent become visible. The activation of a Reaction has "exactly once" semantics, 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/responses are never sent to any recipient component. In case of failures, the firing of a Reaction will be retried by the Databus for a pre-configured number of attempts. The Reaction is marked as completed, with exception completion status, if the maximum retry attempts are reached.
In traditional database machines, where the duration of a transaction is short, abnormal situations cause the whole transaction to be undone, so all performed work is lost and needs to be submitted again for execution. Since Interactions have usually long duration and contain a large number of Reaction(s), AC4J provides additional mechanisms to handling exceptions (such as an Oracle9iAS node crash or an Oracle database node crash).
A Reaction is automatically persisted in the Databus by AC4J 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 crashes, all Reactions that were running and did not end successfully are rolled back. Then, the interrupted Reactions will be re-executed by another OC4J instance.
AC4J uses a mechanism to capture, propagate, and match the application state and control flow information needed for resuming an application after the crash. 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) in order to facilitate the storage of the control flow descriptors or the storage of data variables.
Figure 8-5 demonstrates how data tokens cause Reactions to fire, and Reactions send new data tokens to other Reactions over the Databus. The Databus coordinates and matches the data tokens with its Reactions.
Once the method completes, the Reaction can send information in the form of a data token to another Reaction. All data tokens are sent asynchronously from one Reaction to another over a data channel known as the AC4J Databus. The AC4J Databus routes the data tokens from a producer Reaction to one or more consumer Reactions.
Before you can execute any Interactions, you must initialize an Oracle9i database as a repository for the AC4J Databus. You must configure it to include the following:
These can be added to your Oracle9i database with scripts that are contained in the ac4j-sql.jar
file that was downloaded with your Oracle9iAS installation. Unzip this JAR file. This JAR file contains a README.TXT that discusses the different SQL command options that are available to you. These are also described below:
In order to create AC4J capabilities, you must execute one of the following SQL scripts as a 'SYS
' user on the same machine as the database.
createall
: To create all of the defaults including the default Databus, AC4J super user, default client user (JEMCLIUSER
).
createjemtablespace
: To create the table space for AC4J system, execute the createjemtablespace
SQL script. You must provide the SYS
username/password, the TNSENTRY
of this database where the Databus is created.
createjem
: To install and create the Databus, execute the createjem
SQL script. This requires the SYS
username/password, TNS_ENTRY
, and an AC4J client username.
createclient
: To create another client on an existing Databus, execute the createclient
SQL script. Provide the SYS username/password, client username/password, and client tablespace.
recreatedatabus
: To recreate an existing Databus, which deletes the existing Databus and all its contents and then re-creates it, execute the recreatedatabus
script. Provide the SYS
username/password and TNSENTRY
of the database where the Databus resides.
recreateclient
: To recreate an existing client, execute the recreateclient
SQL script. Provide the SYS username/password and the client username/password.
The Interaction supports JTA global transactions within the database that the Databus exists in. Thus, you need a non-emulated data source for the super user to handle the two-phase commit and a non-emulated data-source for the client to send its asynchronous requests to the Databus. See the DataSource and JTA Chapters in the Oracle9iAS Containers for J2EE Services Guide for a full description of this configuration.
For our purchase order example, the following data sources are configured in the data-sources.xml
file for the two-phase commit.
<!--NON-Emulated DataSource for two-phase commit used by super user--> <data-source class="com.evermind.sql.OrionCMTDataSource" location="jdbc/jemSuperuserDS" username="jemuser" password="jempasswd" url="jdbc:oracle:thin:@<host>:<port>:<ORCL-SID>" inactivity-timeout="60" > <property name="dblink" value="JEMLOOPBACKLINK.REGRESS.RDBMS.DEV.US.ORACLE.COM" /> </data-source> <!--NON-Emulated DataSource for the client user --> <data-source class="com.evermind.sql.OrionCMTDataSource" location="jdbc/jemClientDS" username="jemcliuser" password="jemclipasswd" url="jdbc:oracle:thin:@<host>:<port>:<ORCL-SID>" inactivity-timeout="60" > <property name="dblink" value="JEMLOOPBACKLINK.REGRESS.RDBMS.DEV.US.ORACLE.COM" /> </data-source>
Both of these users were created as default with the SQL scripts listed earlier. The jemuser is the super username and the jemcliuser is the default client username. The DBLINK
is the link to the database that contains the Databus. For the super user data source, this is a loopback link.
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 portion of the purchase order example listed in Figure 7-6. The code sample does not show error handling or import statements to simplify the example. Download the full example off the OTN site.
For the purchase order, the POInteraction
is created. Within the Interaction, several business tasks exist as follows:
takeOrder
Process)
checkINV
Process)
checkCRED
Process)
procPO
Reaction)
Our example is, as follows:
takeOrder
Reaction, which pushes 2 data tokens:
procPO
, which acts on the responses from the inventory and credit check Processes. If the inventory is available and the credit check goes well, then the procPO
returns the purchase order confirmation to the client.
Figure 8-6 illustrates the information flow inside an Interaction. Figure 8-6 also demonstrates how all of the Reactions act on data tokens and provide data tokens to other Processes. This assumes that the customer data has already been made available to the takeOrder
Process. The numbers designate the order in which they fire. That is, the procPO
is dependent on data tokens from both the checkINV
and checkCRED
Processes; thus, it cannot fire until both return their responses back to the takeOrder
Process.
The steps involved in processing the Purchase Order example of Figure 8-6 can be summarized as follows:
takeOrder
Process starts a takeOrder
base Reaction. This base Reaction starts a new purchase order. To complete the purchase order, it must do three things:
checkINV
Process of JEMInventoryBean to verify that the items are in inventory.
checkCRED
Process of JEMCreditBean to verify that the customer's credit is satisfactory.
procPO
Reaction in the current Process to receive the results from the above two Processes.
checkINV
and checkCRED
Processes return responses to the takeOrder
Process.
procPO
Reaction, within takeOrder
Process, reacts to the information provided by the checkINV
and checkCRED
Processes. If satisfactory, it sends the confirmation to the client through the AC4J Databus.
The following code sample shows steps involved in performing loosely-coupled interactions in AC4J.
public static void main(String[] args) throws ClassNotFoundException, Exception {// 0. create a JNDI context Context context = new InitialContext(); // 1.}lookup a datasource wher Databus exists
DataSource clientDS = (DataSource)
context.lookup("java:comp/env/jdbc/jemClientDS");
// 2. Get a JDBC-connection to the database where Databus resides
Connection conn = clientDS.getConnection("jemcliuser", "jemclipasswd");
// 3. Create an AC4J connection using the JDBC connection
JEMConnection AC4JConn = new JEMConnection(conn);
// 4. Create an AC4J session over an AC4J connection to the Databus
JEMSession AC4JSess = new JEMSession(jemconn);
// 5. Lookup the Active EJB handle using the jem-name defined
// in the orion-ejb-jar.xml
JEMHandle activeEJBHandle = (JEMHandle)context.lookup("JEMPurchaseOrderBean");
// 6. Gather the base Reaction input parameters. These input parameters are
// required by the receiving method,
takeOrder.Object[] inputParams = new Object[] { (Object) new String("user1"),
(Object) new Integer("1234-119"),
(Object) new String("pens"),
(Object) new Integer("3") };
// 7. Create the Process Context, Interaction-ID and Activation ID.
// NOTE: IID = "user1" = requestor's name
// AID = AID = AID_105_user1 = AID_<PO_number>_<cust_name>
// 8. Make the call over the AC4J session providing the parameters.
JEMEmitToken req = AC4JSess.call("user1", "AID_105_user1",
activeEJBHandle, "takeOrder",
null, inputParams, null, 0, 0);
// 9. Commit the changes to the Databus by committing the transaction
conn.commit();
// 10. The client must close the AC4J sess
ion and connection because it // does not exist within an AC4J container, which would normally // close these. conn.close(); jemconn.close(); jemsess.close();
The client exists outside of an AC4J server and is requesting a service from an Active EJB through the AC4J Databus. The AC4J Databus is the conduit and controls the asynchronous communication between the client and all Reactions. Thus, every client residing outside of an AC4J server must first connect to the AC4J Databus and create a new session for interaction to occur.
After you have retrieved a connection to the AC4J Databus and created an AC4J session within it, you can send asynchronous messages to Active EJBs in the same or other AC4J instances. The AC4J Databus coordinates the asynchronous messages and acts as a transactional manager for all AC4J beans involved in the transaction. The steps involved in creating a AC4J-session and completing the client's request are described in the following subsection.
The following steps explain the details of a set of steps involved in creating a AC4J session on the AC4J Databus. These steps are sub-set of steps shown in Example 8-2 (Numbered 0-to-4).
An AC4J connection exists above a JDBC connection. Perform the following:
DataSource
defined for the database acting as the AC4J conduit. The DataSource
used should be defined in the data-sources.xml
as a non-emulated datasource with a <dblink>
defined to the database where the AC4J Databus resides. See "AC4J Databus XML Configuration" for more information.
Context context = new InitialContext(); DataSource clientDS = (DataSource) context.lookup("java:comp/env/jdbc/jemClientDS");
DataSource
object.
OracleConnection conn = (OracleConnection)clientDS.getConnection();
JEMConnection AC4JConn = new JEMConnection(conn);
JEMSession AC4JSess = new JEMSession(AC4Jconn);
After you have created an AC4J session on the AC4J Databus, the client can send asynchronous messages to Active EJBs. The client must provide the Active EJB handle, the Process handle, and all of the required input parameters to the base Reaction. The following steps explain the details of the call
that client needs to make in order to complete the AC4J request.
String iid = "user1"; // = customer_name String p_aid = "AID_105_user1"; // = AID_<PO_number>_<customer_name> JEMEmitToken req = AC4JSess.call(iid, p_aid, ..all other parameters..);
JEMEmitToken req =
AC4JSess.call
(null, null, ...all other parameters...);
JEMPortHandle portHandle = req.getPortHandle();
String iid = portHandle.getIid();
String p_aid = portHandle.getAid();
JEMHandle
that identifies an active EJB. The Active EJB handle can be obtained by looking up the jem-name
defined in the orion-ejb-jar.xml
(see Active EJB Deployment pp 7-32).
Context context = new InitialContext(); JMHandle activeEJBHandle = (JEMHandle) context.lookup("JEMPurchaseOrderBean"); JEMEmitToken req = AC4JSess.call(...., activeEJBHandle, ....);
// collect input values for the takeOrder method Object[] inputParams = new Object[] { (Object) new String("user1"), (Object) new Integer("1234-123"), (Object) new String("pens"), (Object) new Integer("3") }; JEMEmitToken req = AC4JSess.call(..., "takeOrder", null, inputParams, ...);
// input parameter types (java-class) for takeOrder method Class[] takeOrderInputClassTypes = new Class[] { String.class, Integer.TYPE, String.class, Integer.TYPE }; // indexes of input parameters you wish to provide for takeOrder method int[] indexOfInputParams = new int[] {0, 1}; // input values corresponding to the indexes for takeOrder method Object[] inputParams = new Object[] { (Object) new String("user1"), (Object) new Integer("1234-123") }; // remember the interaction and process-activation ids for this call JEMEmitToken req = AC4JSess.call(iid, p_aid, ..., "takeOrder", takeOrderInputClassTypes, indexOfInputParams, inputParams, ...);
// input parameter types (java-class) for takeOrder method Class[] takeOrderInputClassTypes = new Class[] { String.class, Integer.TYPE, String.class, Integer.TYPE }; // indexes of input parameters you wish to provide for takeOrder method // NOTE: now, client provides the last 2-input parameters int[] indexOfInputParams = new int[] {2, 3}; // input values corresponding to the indexes for takeOrder method Object[] inputParams = new Object[] { (Object) new String("pens"), (Object) new Integer("3") }; // use the same interaction and process activation ids as those in the // previous call JEMEmitToken req = AC4JSess.call(iid, p_aid, ..., "takeOrder", takeOrderInputClassTypes, indexOfInputParams, inputParams, ...);
JEMSession::call
method.
When a Reaction wants to provide data to an active EJB method (to the base Reaction of the Process), it executes a JEMSession::call with this information. The JEMSession::call contains the Interaction identifier that the EJB is involved in, 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 are optional and can be omitted or can be null, in which case the system automatically creates them. The Databus identifies the context where the Process can be found and routes the data tokens to the intended Process. Thus, all EJB calls are invoked asynchronously through the mediation of the Databus.
conn.commit();
conn.close(); // client as well an application-code must close jemconn.close(); // client must close jemsess.close(); // client must close
Once the client commits the request, the AC4J Databus matches the data-tokens provided by the client with that of the requested Reaction, and internally schedules the instantiation of the JEMPurchaseOrderBean Active EJB and activation of the takeOrder
Process. The takeOrder
Process starts a takeOrder
base Reaction which starts a new purchase order. As discussed in Figure 7-6, this reaction, takeOrder
, processes the client's request by invoking additional services from the other Active EJBs, JEMInventoryBean and JEMCreditBean, as shown in the following code sample:
public void takeOrder(String clientName, int creditCardNumber,
String itemName, int quantity) throws RemoteException, TestException {// 0. create a JNDI context Context context = new InitialContext(); // 1. Retrieve the current AC4J Reaction. JEMReaction currentAC4JReaction = (JEMReaction) JEMReaction.getReaction(); // 2. Lookup the Active EJB handle using the jem-name defined // in the orion-ejb-jar.xml JEMHandle activeInvHandle = (JEMHandle) context.lookup("JEMInventoryBean"); // 3. Gather all input and return parameters for the checkINV Reaction. // Define input and return parameter types and the parameter values Object[] checkINVInputParamValues = new Object[] { (Object)itemName, (Object) new Integer (quantity) }; Class[] checkINVReturnClassType = new Class[] { Boolean.TYPE }; // 4. Request a service from JEMBeancheckINV through Databus JEMEmitToken inventoryRequest= currentAC4JReaction.call(activeInvHandle, "checkINV", null, checkINVInputParamValues, checkINVReturnClassType, null, null, 0, 0); // 5. Repeat Steps 2-4 above to request a service from another // Active EJB, JEMCreditBean. The returned JEMEmitToken is // named creditRequest. // 6. Register a Reaction, procPO, that will be activated when the // responses from the above two asynchronous calls to the // active-EJBs return Class[] procPOInputClassTypes = new Class[] { Boolean.TYPE, String.class }; JEMEmitToken[] requests = new JEMEmitToken[] { inventoryRequest, creditRequest }; currentJEMReaction.registerReaction ("procPO", procPOInputClassTypes, requests, 1, null, null, 0);}
The AC4J Databus instantiates the Active EJB, JEMPurchaseOrderBean (corresponding to the JEMHandle provided by the client), in an AC4J server. The takeOrder
Process starts a takeOrder
base Reaction. The steps involved in the completion of this initiation process are described below:
takeOrder
, is running in an AC4J server. Hence, it already has a Process Context and can be used by the application (or Active Bean) code. The application code can retrieve the Process Context through the Demarcation as follows:
// retrieve the current-Reaction context--a static method JEMReaction currentAC4JReaction = (JEMReaction) JEMReaction.getReaction(); String iid = currentAC4JReaction.getIid(); String p_aid = currentAC4JReaction.getAid();
Context context = new InitialContext(); // call to JEMInventoryBean JEMHandle activeInvHandle = (JEMHandle) context.lookup("JEMInventoryBean"); JEMEmitToken inventoryRequest= currentAC4JReaction.call(activeInvHandle, .....); // call to JEMCreditBean JEMHandle activeCreditHandle = (JEMHandle) context.lookup("JEMCreditBean"); JEMEmitToken creditRequest= currentAC4JReaction.call(activeCreditHandle, .....);
takeOrder
Reaction) provides the base Reaction (Method) name, the return parameter's java-class type and all or part of it's input parameters that it wishes to call. In the current example, the client provides all the input parameters needed by the called Reactions (checkINV, CheckCRED) as follows:
// collect input values for the checkINV method Object[] checkINVInputParamValues = new Object[] { (Object)itemName, (Object) new Integer (quantity) }; // state the return Class type of checkINV method Class[] checkINVReturnClassType = new Class[] { Boolean.TYPE }; // make the call to the checkINV method JEMEmitToken inventoryRequest= currentAC4JReaction.call(..., "checkINV", null, checkINVInputParamValues, checkINVReturnClassType, ....); // collect input values for the checkCRED method Object[] checkCreditInputParamValues = new Object[] { (Object) clientName, (Object) new Integer (creditCardNumber), (Object) new Float (quantity * 1.4) }; // state the return Class type of checkINV method Class[] checkCreditReturnClassType = new Class[] { String.class }; // make the call ro checkCRED method JEMEmitToken creditRequest= currentAC4JReaction.call(..., "checkCRED", null, checkCreditInputParamValues, checkCreditReturnClassType, ....);
Class[] procPOInputClassTypes = new Class[] { Boolean.TYPE, String.class }; JEMEmitToken[] requests = new JEMEmitToken[] { inventoryRequest, creditRequest }; currentJEMReaction.registerReaction ("procPO", procPOInputClassTypes, requests, 1, null, null, 0);
The takeOrder
base Reaction is completed only after the AC4J infrastructure commits the transaction which includes the calls to the other two Active EJBs and a registered Reaction. The "checkINV
" and "checkCRED
" Processes receive the requests from the AC4J Databus as if it 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 from the AC4J Databus, which were initiated from the takeOrder
Reaction. Both of them receive the request, perform their tasks, and return. The returned values are forwarded by the AC4J Databus to the registered Reaction--procPO
.
The following code sample shows the checkINV
method. The checkCRED
method is similar in its AC4J responsibilities.
public boolean checkINV(String itemName, int quantity) throws RemoteException, TestException {boolean inventoryExists = false; // The logic in the next step is ommitted inventoryExists = query it's own database for the item and quantity; return inventoryExists;}
Both checkINV
and checkCRED
Processes return the responses to the procPO
Reaction through the AC4J Databus. The AC4J Databus makes sure that the return data-tokens have valid takeOrder
Process Context and matches the input parameter types of the procPO
Reaction. When both parameters arrive the procPO Reaction fires and executes the procPO method of the JEMPurchaseOrderBean Active EJB, which reacts to the information provided by the checkINV
and checkCRED
Processes. It completes the client's request by posting the result to the AC4J Databus.
public String procPO(boolean inventoryExists, String creditInfo) throws RemoteException, TestException { String poStatus = "Not Shipped"; if(creditInfo == null) return poStatus; if (inventoryExists) { if(creditInfo.equalsIgnoreCase("Credit approved")) poStatus = "Shipped"; else if (creditInfo.equalsIgnoreCase("Credit failed")) poStatus = "Credit failed"; } else poStatus = "Items unavailable"; return poStatus; }
The client needs to know the response to it's purchase order request. As stated earlier, each request (or call) is identified by a Process Context (Interaction ID and Activation ID). Using the Process Context the client can pull the response from the AC4J Databus.
The received JEMEmitToken from the response can then be parsed by the client. If the client existed inside the OC4J container, the container would deconstruct the JEMEmitToken to the required type. Instead, the client must parse out the response correctly as shown below:
public static void main(String[] args) throws ClassNotFoundException, Exception {// 0. create a JNDI context Context context = new InitialContext(); // 1. lookup a datasource wher Databus exists DataSource clientDS = (DataSource) context.lookup("java:comp/env/jdbc/jemClientDS"); // 2. Get a JDBC-connection to the database where Databus resides Connection conn = clientDS.getConnection("jemcliuser", "jemclipasswd"); // 3. Create an AC4J connection using the JDBC connection JEMConnection AC4JConn = new JEMConnection(conn); // 4. Create an AC4J session over an AC4J connection to the Databus JEMSession AC4JSess = new JEMSession(jemconn); // 5. Lookup the Active EJB handle using the jem-name defined // in the orion-ejb-jar.xml JEMHandle activeEJBHandle = (JEMHandle) context.lookup("JEMPurchaseOrderBean"); // 6. Retrieve the Response using the Process context with which // the initial request was made. JEMEmitToken rcvresp = AC4JSess.receiveReactionResponse ("user1", "AID_105_user1", activeEJBHandle, "takeOrder", 0); // 7. The getReactionResponseObjectInstance method parses the returned // parameter into an java.lang.Object. Object obj = rcvresp.getReactionResponseObjectInstance(); // 8. Print out results if (obj instanceof java.lang.String)String ret = (String) obj;// 9. The client must commit the transaction conn.commit(); // 10. The client must close the AC4J session and connection because it // does not exist within an AC4J container, which would normally // close these. conn.close(); jemsess.close(); jemconn.close();}
As seen earlier, procPO reaction reacts to the information provided by the checkINV
and checkCRED
Processes. It completes the client's request by posting the result to the AC4J Databus. The client must connect to the AC4J Databus to retrieve it's response by providing a proper Process Context. The steps involved in connecting to the AC4J Databus were described earlier. After receiving the response the client can retrieve a java.lang.Object instance which must be processed further.
After creating a AC4J session on the AC4J Databus, the client can retrieve the response by doing the following steps:
String iid = "user1"; // = customer_name String p_aid = "AID_105_user1"; // = AID_<PO_number>_<customer_name> JEMEmitToken rcvresp = AC4JSess.receiveReactionResponse (iid, p_aid, ...);
jem-name
defined in the orion-ejb-jar.xml
(see "AC4J Active EJB Deployment").
Context context = new InitialContext(); JMHandle activeEJBHandle = (JEMHandle) context.lookup("JEMPurchaseOrderBean"); JEMEmitToken rcvresp = AC4JSess.receiveReactionResponse (..., activeEJBHandle, ...);
takeOrder
Process.
JEMEmitToken rcvresp = AC4JSess.receiveReactionResponse (..., "takeOrder", ...);
receiveReactionResponse
can be used to retrieve the Object instance as follows:
Object obj = rcvresp.getReactionResponseObjectInstance();
conn.commit();
The active EJB is developed as any other EJB. The changes that enable the EJB to be used in an AC4J Interaction is 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 Databus 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 defined in the ejb-jar.xml
file as an active EJB. It 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, who 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 Databus. For example, JEMCLIUSER is the user name that was used 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>
The following is the entire orion-ejb-jar.xml
file for the three Active EJBs.
<?xml version="1.0"?> <!DOCTYPE orion-ejb-jar PUBLIC "-//Evermind//DTD Enterprise JavaBeans 1.1 runtime//EN" "http://www.orionserver.com/dtds/orion-ejb-jar.dtd"> <orion-ejb-jar deployment-version="1.4.5" deployment-time="e60dffcea9"> <enterprise-beans> <jem-server-extension data-source-location="jdbc/nonEmulatedDS" scheduling-threads="1"> <description>AC4J deployment</description> </jem-server-extension> <jem-deployment jem-name="JEMPurchaseOrderBean" ejb-name="PurchaseOrderBean"> <description>Active Purchase Order bean</description> <called-by> <caller caller-identity="JEMCLIUSER"/> </called-by> <security-identity> <description>using the caller identity </description> <use-caller-identity>true</use-caller-identity> </security-identity> </jem-deployment> <jem-deployment jem-name="JEMInventoryBean" ejb-name="InventoryBean"> <description>Active Inventory bean</description> <called-by> <caller caller-identity="JEMCLIUSER"/> </called-by> <security-identity> <description>using the caller identity </description> <use-caller-identity>true</use-caller-identity> </security-identity> </jem-deployment> <jem-deployment jem-name="JEMCreditBean" ejb-name="CreditBean"> <description>Active Credit bean</description> <called-by> <caller caller-identity="JEMCLIUSER"/> </called-by> <security-identity> <description>using the caller identity </description> <use-caller-identity>true</use-caller-identity> </security-identity> </jem-deployment> </enterprise-beans>
|
Copyright © 2002 Oracle Corporation. All Rights Reserved. |
|