More on ADF Business Components and Fusion Page Runtime

0
123
10 min read

(For more resources related to this topic, see here.)

Lifecycle of an ADF Fusion web page with region

When a client requests for a page with region, at the server, ADF runtime intercepts and pre-processes the request before passing it to the page lifecycle handler. The pre-processing tasks include security check, initialization of Trinidad runtime, and setting up the binding context and ADF context. This is shown in the following diagram:

After setting up the context for processing the request, the ADF framework starts the page lifecycle for the page. During the Before Restore View phase of the page, the framework will try to synchronize the controller state with the request, using the state token sent by the client. If this is a new request, a new root view port is created for the top-level page. In simple words, a view port maps to a page or page fragment in the current view. During view port initialization, the framework will build a data control frame for holding the data controls. During this phrase runtime also builds the binding containers used in the current page. The data control frame will then be added to the binding context object for future use. After setting up the basic infrastructure required for processing the request, the page lifecycle moves to the Restore View phase.

During the Restore View phase, the framework generates a component tree for the page. Note that the UI component tree, at this stage, contains only metadata for instantiating the UI components. The component instantiation happens only during the Render Response phase, which happens later in the page lifecycle. If this is a fresh request, the lifecycle moves to the Render Response phase. Note that, in this article, we are not discussing how the framework handles the post back requests from the client.

During the Render Response phase, the framework instantiates the UI components for each node in the component tree by traversing the tree hierarchy. The completed component tree is appended to UIViewRoot, which represents the root of the UI component tree.

Once the UI components are created, runtime walks through the component tree and performs the pre-rendering tasks. The pre-rendering event is used by components with lazy initialization abilities, such as region, to keep themselves ready for rendering if they are added to the component tree during the page cycle. While processing a region, the framework creates a new child view port and controller state for the region, and starts processing the associated task flow. The following is the algorithm used by the framework while initializing the task flow:

  1. If the task flow is configured not to share data control, the framework creates a new data control frame for the task flow and adds to the parent data control frame (the data control frame for the parent view port).

  2. If the task flow is configured to start a new transaction, the framework calls beginTransaction() on the control frame.

  3. If the task flow is configured to use existing transaction, the framework asks the data control frame to create a save point and to associate it to the page flow stack.

  4. If the task flow is configured to ‘use existing transaction if possible’, framework will start a new transaction on the data control, if there is no transaction opened on it. If a transaction is already opened on the data control, the framework will use the existing one.

Once the pre-render processing is over, each component will be asked to write out its value into the response object. During this action, the framework will evaluate the EL expressions specified for the component properties, whenever they are referred in the page lifecycle. If the EL expressions contain binding expression referring properties of the business components, evaluation of the EL will end up in instantiating corresponding model components. The framework performs the following tasks during the evaluation of the model-bound EL expressions:

  • It instantiates the data control if it is missing from the current data control frame.

  • It performs a check out of the application module.

  • It attaches the transaction object to the application module. Note that it is the transaction object that manages all database transactions for an application module. Runtime uses the following algorithm for attaching transactions to the application module:

    1. If the application module is nested under a root application module or if it is used in a task flow that has been configured to use an existing transaction, the framework will identify the existing DBTransaction object that has been created for the root application module or for the calling task flow, and attach it to the current application module.

      Under the cover, the framework uses the jbo.shared.txn parameter (named transaction) to share the transaction between the application modules. In other words, if an application module needs to share a transaction with another module, the framework assigns the same jbo.shared.txn value for both application modules at runtime. While attaching the transaction to the application module, runtime will look up the transaction object by using the jbo.shared.txn value set for the application module and if any transaction object is found for this key, it re-uses the same.

    2. If the application module is a regular one, and not part of the task flow that shares a transaction with caller, the framework will generate a new DBTransaction object and attach it to the application module.

  • After initializing the data control, the framework adds it to the data control frame. The data control frame holds all the data control used in the current view port. Remember that a, view port maps to a page or a page fragment.

  • Execute an appropriate view object instance, which is bound to the iterator.

At the end of the render response phase, the framework will output the DOM content to the client. Before finishing the request, the ADF binding filter will call endRequest() on each data control instance participating in the request. Data controls use this callback to clean up the resources and check in the application modules back to the pool.

Transaction management in Fusion web applications

A transaction for a business application may be thought of as a unit of work resulting in changes to the application state. Oracle ADF simplifies the transaction handling by abstracting the micro-level management of transactions from the developers. This section discusses the internal aspects of the transaction management in Fusion web applications. In this discussion, we will consider only ADF Business Component-based applications.

What happens when the task flow commits a transaction

Oracle ADF allows you to define transactional boundaries, using task flows. Each task flow can be configured to define the transactional unit of work.

Let us see what happens when a task flow return activity tries to commit the currently opened transaction. The following is the algorithm used by the framework when the task flow commits the transaction:

  1. When you action a task flow return activity, a check is carried over to see whether the task flow is configured for committing the current transaction or not. And if found true, runtime will identify the data control frame associated with the current view port and call the commit operation on it.

  2. The data control frame delegates the “commit” call to the transaction handler instance for further processing. The transaction handler iterates over all data controls added to the data control frame and invokes commitTransaction on each root data control. It is the transaction handler that engages all data controls added to the data control frame in the transaction commit cycle.

  3. Data control delegates the commit call to the transaction object that is attached to the application module. Note that if you have a child task flow participating in the transaction started by the caller or application modules nested under a root application module, they all share the same transaction object. The commit call on a transaction object will commit changes done by all application modules attached to it.

The following diagram illustrates how transaction objects that are attached to the application modules are getting engaged when a client calls commit on the data control frame:

Programmatically managing a transaction in a task flow

If the declarative solution provided by the task flow for managing the transaction is not flexible enough to meet your use case, you can handle the transaction programmatically by calling the beginTransaction(), commit(), and rollback() methods exposed by oracle.adf.model.DataControlFrame.

The data control frame acts as a bucket for holding data controls used in a binding container (page definition file). A data control frame may also hold child data control frames, if the page or page fragment has regions sharing the transaction with the parent. When you call beginTransaction(), commit(), or rollback() on a data control frame, all the data controls added to the data control frame will participate in the appropriate transaction cycle. In plain words, the data control frame provides a mechanism to manage the transaction seamlessly, freeing you from the pain of managing transactions separately for each data control present in the page definition. Note that you can use the DataControlFrame APIs for managing a transaction only in the context of a bounded task flow with an appropriate transaction setting (in the context of a controller transaction).

The following example illustrates the APIs for programmatically managing a transaction, using the data control frame:

//In managed bean class public void commit(){ //Get the binding context BindingContext bindingContext = BindingContext. getCurrent(); //Gets the name of current(root) DataControlFrame String currentFrame = bindingContext.getCurrentDataControlFrame(); //Finds DataControlFrame instance DataControlFrame dcFrame = bindingContext.findDataControlFrame(currentFrame); try { // Commit the trensaction dcFrame.commit(); //Open a new transaction allowing user to continue //editing data dcFrame.beginTransaction(null); } catch (Exception e) { //Report error through binding container ((DCBindingContainer)bindingContext. getCurrentBindingsEntry()). reportException(e); } }

Programmatically managing a transaction in the business components

The preceding solution of calling the commit method on the data control frame is ideal to be used in the client tier in the context of the bounded task flows. What if you need to programmatically commit the transaction from the business service layer, which does not have any binding context?

To commit or roll back the transactions in the business service layer logic where there is no binding context, you can call commit() or rollback() on the oracle.jbo.Transaction object associated with the root application modules. The following example shows a method defined in an application module, which invokes commit on the Transaction object attached to the root application module:

//In application module implementation class
/**
*This method calls commit on transaction object
*/
public void commit(){
this.getRootApplicationModule().getTransaction().commit();
}

Sharing a transaction between application modules at runtime

An application module, nested under a root application module, shares the same transaction context with the root. This solution will fit well if you know that the application module needs to be nested during the development phase of the application. What if an application module needs to invoke the business methods from various application modules whose names are known only at runtime, and all the method calls require to happen in same transaction? You can use the following API in such scenarios to create the required application module at runtime:

DBTransaction::createApplicationModule(defName);

The following method defined in a root application module creates a nested application module on the fly. Both calling and called application modules share the same transaction context.

//In application module implementation class
/**
* Caller passes the AM definition name of the application
* module that requires to participate in the existing
* transaction. This method creates new AM if no instance is
* found for the supplied amName and invokes required service
* on it.
* @param amName
* @param defName
*/
public void nestAMIfRequiredAndInvokeMethod(String amName, String
defName) {
//TxnAppModule is a generic interface implemented by all
//transactional AMs used in this example
TxnAppModule txnAM = null;
boolean generatedLocally = false;
try {
//Check whether the TxnAppModuleImpl is already nested
txnAM = (TxnAppModule)getDBTransaction().
getRootApplicationModule().
findApplicationModule(amName);
//create a new nested instance of the TxnAppModuleImpl,
// if not nested already
if(txnAM == null) {
txnAM = (TxnAppModule)this.
getDBTransaction().
createApplicationModule(defName);
generatedLocally = true;
}
//Invoke business methods
if (txnAM != null) {
txnAM.updateEmployee();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//Remove locally created AM once use is over
if (generatedLocally && txnAM != null) {
txnAM.remove();
}
}
}

LEAVE A REPLY

Please enter your comment!
Please enter your name here