Fusion Middleware Documentation
Advanced Search


Developing Fusion Web Applications with Oracle Application Development Framework
Close Window

Table of Contents

Show All | Collapse

49 Using State Management in a Fusion Web Application

This chapter describes the Fusion web application state management facilities and how to use them to specify the release level for ADF application modules to support stateful applications on the web.

This chapter includes the following sections:

49.1 Understanding Why State Management is Necessary

Most real-world business applications need to support multi-step user tasks. Modern sites tend to use a step-by-step style user interface to guide the end user through a logical sequence of pages to complete these tasks. When the task is done, the user can save or cancel everything as a unit. However, the HTTP protocol is built on the concept of individual, stateless requests, so the responsibility of grouping a sequence of user actions into a logical, stateful unit of work falls to the application developer.

49.1.1 Examples of Multi-Step Tasks

In a typical search-then-edit scenario, the end user searches to find an appropriate row to update, then may open several different pages of related master-detail information to make edits before deciding to save or cancel work. Consider another scenario where the end user wants to book a vacation online. The process may involve the end user's entering details about:

  • One or more flight segments that comprise the journey

  • One or more passengers taking the trip

  • Seat selections and meal preferences

  • One or more hotel rooms in different cities

  • Car they will rent

Along the way, the user might decide to complete the transaction, save the reservation for finishing later, or abandon the whole thing.

It's clear these scenarios involve a logical unit of work that spans multiple web pages. You've seen in previous chapters how to use JDeveloper's JSF page navigation diagram to design the page flow for these use cases, but that is only part of the puzzle. The pending changes the end user makes to business domain objects along the way — Trip, Flight, Passenger, Seat, HotelRoom, Auto, etc. — represent the in-progress state of the application for each end user. Along with this, other types of "bookkeeping" information about selections made in previous steps comprise the complete picture of the application state.

49.1.2 Stateless HTTP Protocol Complicates Stateful Applications

While it may be easy to imagine these multi-step scenarios, implementing them in web applications is complicated by the stateless nature of HTTP, the hypertext transfer protocol. Figure 49-1 illustrates how an end user's visit to a site comprises a series of HTTP request/response pairs. However, HTTP affords a web server no way to distinguish one user's request from another user's, or to differentiate between a single user's first request and any subsequent requests that user makes while interacting with the site. The server gets each request from any user always as if it were the first (and only) one they make.

Figure 49-1 Web Applications Use the Stateless HTTP Protocol

Web applications and stateless HTTP protocol

49.1.3 How Cookies Are Used to Track a User Session

As shown in Figure 49-2, the technique used to recognize an ongoing sequence of requests from the same end user over the stateless HTTP protocol involves a unique identifier called a cookie. A cookie is a name/value pair that is sent in the header information of each HTTP request the user makes to a site. On the initial request made by a user, the cookie is not part of the request. The server uses the absence of the cookie to detect the start of a user's session of interactions with the site, and it returns a unique identifier to the browser that represents this session for this user. In practice, the cookie value is a long string of letters and numbers, but for the simplicity of the illustration, assume that the unique identifier is a letter like "A" or "Z" that corresponds to different users using the site.

Web browsers support a standard way of recognizing the cookie returned by the server that allows the browser to identify the following:

  • the site that sent the cookie

  • how long it should remember the cookie value

On each subsequent request made by that user, until the cookie expires, the browser sends the cookie along in the header of the request. The server uses the value of the cookie to distinguish between requests made by different users.

Cookies can be set to live beyond a single browser session so that they might expire in a week, a month, or a year from when they were first created, while a session cookie expires when you close your browser.

Figure 49-2 Tracking State Using a Session Cookies and Server-Side Session

Tracking state cookies in server-side sessions flow

Java EE-compliant web servers provide a standard server-side facility called the HttpSession that allows a web application to store Java objects related to a particular user's session as named attribute/value pairs. An object placed in this session Map on one request can be retrieved by the application while handling a subsequent request during the same session.

The session remains active while the user continues to send new requests within the timeframe specified by the <session-timeout> element in the web.xml file. The default session length is typically around 30 minutes, depending on the container.

49.1.4 Performance and Reliability Impact of Using HttpSession

The HttpSession facility is an ingredient in most application state management strategies, but it can present performance and reliability problems if not used judiciously. First, because the session-scope Java objects you create are held in the memory of the Java EE web server, the objects in the HTTP session are lost if the server should fail.

As shown in Figure 49-3, one way to improve the reliability is to configure multiple Java EE servers in a cluster. By doing this, the Java EE application server replicates the objects in the HTTP session for each user across multiple servers in the cluster so that if one server goes down, the objects exist in the memory of the other servers in the cluster that can continue to handle the users requests. Since the cluster comprises separate servers, replicating the HTTP session contents among them involves broadcasting the changes made to HTTP session objects over the network.

Figure 49-3 Session Replication in a Server Cluster

Flow of session replication in server cluster

You can begin to see some of the performance implications of overusing the HTTP session:

  • The more active users, the more HTTP sessions will be created on the server.

  • The more objects stored in each HTTP session, the more memory you will need. Note that the memory is not reclaimed when the user becomes inactive; this only happens with a session timeout or an explicit session invalidation. Session invalidations don't always happen because users don't always logout.

  • In a cluster, the more objects in each HTTP session that change, the more network traffic will be generated to replicate the changed objects to other servers in the cluster.

At the outset, it would seem that keeping the number of objects stored in the session to a minimum addresses the problem. However, this implies leveraging an alternative mechanism for temporary storage for each user's pending application state. The most popular alternatives involve saving the application state to the database between requests or to a file of some kind on a shared file system.

Of course, this is easier said than done. A possible approach involves eagerly saving the pending changes to your underlying database tables and committing the transaction at the end of each HTTP request. But this idea has two key drawbacks:

  • Your database constraints might fail.

    At any given step of the multi-step process, the information may only be partially complete, and this could cause errors at the database level when trying to save the changes.

  • You complicate rolling back the changes.

    Cancelling the logical of unit of work would involve carefully deleting all of the eagerly committed rows in possible multiple tables.

These limitations have led developers in the past to invent solutions involving a "shadow" set of database tables with no constraints and with all of the column types defined as character-based. Using such a solution becomes very complex very quickly. Ultimately, you will conclude that you need some kind of generic application state management facility to address these issues in a more generic and workable way. The solution comes in the form of ADF Business Components, which implements this for you out of the box.

49.2 Introduction to Fusion Web Application State Management

State management enables you to easily create web applications that support multi-step use cases without falling prey to the memory, reliability, or implementation complexity problems described in Section 49.1, "Understanding Why State Management is Necessary."

Application state management is provided at two levels, by the Save For Later feature in a task flow, and application module state management in the model layer.

Save For Later is activated at the controller layer and automatically saves a "snapshot" of the current UI and controller states, and delegates to the model layer to passivate (save) its state as well.

If you are not using ADF data controls, you can still use application module state management alone, but since this will save only the model state, this is an outside case for most applications.

49.2.1 Basic Architecture of the Save for Later Facility

Save for Later saves an incomplete transaction without enforcing validation rules or submitting the data. The end user can resume working on the same transaction later with the same data that was originally saved when the application was exited.

49.2.2 Basic Architecture of the Application Module State Management Facility

Your ADF Business Components-based application automatically manages the application state of each user session. This provides the simplicity of a stateful programming model that you are accustomed to in previous 4GL tools, yet implemented in a way that delivers scalability nearing that of a purely stateless application. Understanding what happens behind the scenes is essential to make the most efficient use of this important feature.

You can use application module components to implement completely stateless applications or to support a unit of work that spans multiple browser pages. Figure 49-4 illustrates the basic architecture of the state management facility to support these multi-step scenarios. An application module supports passivating (storing) its pending transaction state to an XML document, which is stored in the database in a single, generic table, keyed by a unique passivation snapshot ID. It also supports the reverse operation of activating pending transaction state from one of these saved XML snapshots. This passivation and activation is performed automatically by the application module pool when needed.

Figure 49-4 ADF Provides Generic, Database-Backed State Management

ADF state management

The ADF binding context is the one object that lives in the HttpSession for each end user. It holds references to lightweight application module data control objects that manage acquiring an application module instance from the pool during the request (when the data control is accessed) and releasing it to the pool at the end of each request. The data control holds a reference to the ADF session cookie that identifies the user session. In particular, business domain objects created or modified in the pending transaction are not saved in the HttpSession using this approach. This minimizes both the session memory required per user and eliminates the network traffic related to session replication if the servers are configured in a cluster.

For improved reliability, serialize your session objects. Objects stored in distributed sessions need to implement the java.io.Serializable interface. Implementing this interface ensures the data can be transported over-the-wire to each server instance in the cluster. Use a custom method like the addObjectToSession(String key, Serializable value) method, instead of the default HttpSession.setAttribute (String key, Object value) method when adding session data. The distinction is, if you were to call the addObjectToSession() method with a nonserializable object, you would see a compile-time error. If you were to try to replicate a session object that had nonserializable objects placed into session with the put() method, you would see a runtime error and potentially, a broken user experience.

Additionally, if you have multiple application servers and you enable the optional ADF Business Components failover support (explained in Section 49.2.2.2, "How Passivation Changes When Optional Failover Mode is Enabled"), then subsequent end-user requests can be handled by any server in your server farm or cluster. The session cookie can reactivate the pending application state from the database-backed XML snapshot if required, regardless of which server handles the request.

49.2.2.1 Understanding When Passivation and Activation Occurs

To better understand when the automatic passivation and activation of application module state occurs, consider the following simple case:

  1. At the beginning of an HTTP request, the application module data control handles the beginrequest event by checking out an application module instance from the pool.

    The application module pool returns an unreferenced instance. An unreferenced application module is one that is not currently managing the pending state for any other user session.

  2. At the end of the request, the application module data control handles the endrequest event by checking the application module instance back into the pool in "managed state" mode.

    That application module instance is now referenced by the data control that just used it. And the application module instance is an object that still contains pending transaction state made by the data control (that is, entity object and view object caches; updates made but not committed; and cursor states), stored in memory. As you'll see below, it's not dedicated to this data control, just referenced by it.

  3. On a subsequent request, the same data control — identified by its SessionCookie — checks out an application module instance again.

    Due to the "stateless with user affinity" algorithm the pool uses, you might assume that the pool returns the exact same application module instance, with the state still there in memory. (To understand this algorithm, read Section 50.1, "About Application Module Pooling" and the discussion of Referenced Pool Size in Section 50.2.7.2, "Pool Sizing Parameters.")

Sometimes due to a high number of users simultaneously accessing the site, application module instances must be sequentially reused by different user sessions. In this case, the application pool must recycle a currently referenced application module instance for use by another session, as follows:

  1. The application module data control for User A's session checks an application module instance into the application pool at the end of a request. Assume this instance is named AM1.

  2. The application module data control for User Z's new session requests an application module instance from the pool for the first time, but there are no unreferenced instances available. The application module pool then:

    • Passivates the state of instance AM1 to the database.

    • Resets the state of AM1 in preparation to be used by another session.

    • Returns the AM1 instance to User Z's data control.

  3. On a subsequent request, the application module data control for User A's session requests an application module instance from the pool. The application module pool then:

    • Obtains an unreference instance.

      This could be instance AM1, obtained by following the same tasks as in Step 2, or another AM2 instance if it had become unreferenced in the meantime.

    • Activates the appropriate pending state for User A from the database.

    • Returns the application module instance to User A's data control.

The process of passivation, activation, and recycling allows the state referenced by the data control to be preserved across requests without requiring a dedicated application module instance for each data control. Both browser users in the above scenario are carrying on an application transaction that spans multiple HTTP requests, but the end users are unaware whether the passivation and activation is occurring in the background. They just continue to see the pending changes. In the process, the pending changes never need to be saved into the underlying application database tables until the end user is ready to commit the logical unit of work.

Note that this keeps the session memory footprint low because the only business component objects that are directly referenced by the session (and are replicable) are the data control and the session cookie.

The application module pool makes a best effort to keep an application module instance "sticky" to the current data control whose pending state it is managing. This is known as maintaining user session affinity. The best performance is achieved if a data control continues to use exactly the same application module instance on each request, since this avoids any overhead involved in reactivating the pending state from a persisted snapshot.

49.2.2.2 How Passivation Changes When Optional Failover Mode is Enabled

The jbo.dofailover parameter controls when and how often passivation occurs. You can set this parameter in your application module configuration on the Pooling and Scalability tab of the Edit Configuration dialog. When the failover feature is disabled, which it is by default, then application module pending state will only be passivated on demand when it must be. This occurs just before the pool determines it must hand out a currently referenced application module instance to a different data control.

The passivation activity (and the corresponding wait) occurs only if failover is disabled and session replication is disabled. If either failover or session replication is enabled, then the recycling thread will reuse the snapshot that was already acquired to support the failover.

Note:

Passivation can also occur when an application module is timed out. For more information about application pool removal algorithms (such as jbo.ampool.timetolive), see Section 50.2.7.3, "Pool Cleanup Parameters."

In contrast, with the failover feature turned on, the application module's pending state is passivated every time it is checked back into application module pool. This provides the most pessimistic protection against application server failure. The application module instances' state is always saved and may be activated by any application module instance at any time. Of course, this capability comes at expense of the additional overhead of eager passivation on each request.

When failover is turned on, a failure can occur when Oracle WebLogic Server is configured to forcibly release connections back into the pool. A failure of this type produces a SQLException (Connection has already been closed.) that is saved to the server log. The exception is not reported through the user interface. To ensure that state passivation occurs and users' changes are saved, the server administrator should set an appropriate value for the weblogic-application.xml deployment descriptor parameter inactive-connection-timeout-seconds on the <connection-check-params> pool params element. Setting the deployment descriptor parameter to several minutes, in most cases, should avoid forcing the inactive connection timeout and the resulting passivation failure. Adjust the setting as needed for your environment.

Best Practice:

By default, the failover feature is disabled (jbo.dofailover=false) as a performance optimization when there is only one web server instance configured, by reducing the need for passivation and activation. This allows for application module affinity to a specific user session.

For high availability, in a clustered server environment, the failover feature will auto-default to enabled (jbo.dofailover=true). This ensures that more application modules are readily available, thereby increasing scalability. In this mode, passivation occurs at the end of every request.

To understand application module pooling and the "stateless with affinity" algorithm, read Section 50.1, "About Application Module Pooling" and the discussion of Referenced Pool Size in Section 50.2.7.2, "Pool Sizing Parameters."

Note:

When running or debugging an application that uses failover support within the JDeveloper environment, you are frequently starting and stopping the application server. The ADF failover mechanism has no way of knowing whether you stopped the server to simulate an application server failure, or whether you stopped it because you want to retest something from scratch in a fresh server instance. If you intend to do the latter, exit out of your browser before restarting the application on the server. This eliminates the chance that you will be confused by the correct functioning of the failover mechanism when you didn't intend to be testing that aspect of your application.

49.2.2.3 About State Management Release Levels

When a data control handles the endrequest notification indicating the processing for the current HTTP request has completed, it releases the application module instance by checking it back into the application module pool. The application module pool manages instances and performs state management tasks (or not) based on the release level you use when returning the instance to the pool.

There are three release levels used for returning an instance of an application module to a pool:

  • Managed - This is the default level, where the application module pool prefers to keep the same application module instance for the same data control, but may release an instance if necessary.

  • Unmanaged - No state needs to be preserved beyond the current request.

  • Reserved - A one-to-one relationship is preserved between an application module instance and a data control.

    Caution:

    In general, it is strongly recommended never to use Reserved release level. You would normally avoid using this mode because the data control to application module correlation becomes one to one, the scalability of the application reduces very sharply, and so does reliability of the application.

49.2.2.3.1 About Managed Release Level

This is the default release level and implies that application module's state is relevant and has to be preserved for this data control to span over several HTTP requests. Managed level does not guarantee that for the next request this data control will receive the same physical application module instance, but it does guarantees that an application module with identical state will be provided so it is logically the same application module instance each time. It is important to note that the framework makes the best effort it can to provide the same instance of application module for the same data control if it is available at the moment. This is done for better performance since the same application module does not need to activate the previous state which it still has intact after servicing the same data control during previous request. However, the data control is not guaranteed to receive the same instance for all its requests and if the application module that serviced that data control during previous is busy or unavailable, then a different application module will activate this data control's state. For this reason, it is not valid to cache references to application module objects, view objects, or view rows across HTTP requests in controller-layer code.

To specify the managed release level for an application module, set the release mode to Stateful.

Note:

If the jbo.ampool.doampooling configuration property is false — corresponding to your unchecking the Enable Application Module Pooling option in the Pooling and Scalability tab of the Edit Configuration dialog — then there is effectively no pool. In this case, when the application module instance is released at the end of a request it is immediately removed. On subsequent requests made by the same user session, a new application module instance must be created to handle each user request, and pending state must be reactivated from the passivation store. Setting this property to false is useful to discover problems in your application logic that might occur when reactivation does occur due to unpredictable load on your system. However, the property jbo.ampool.doampooling set to false is not a supported configuration for production applications and must be set to true before you deploy your application. For further details, see Section 49.10, "Testing to Ensure Your Application Module is Activation-Safe."

49.2.2.3.2 About Unmanaged Release Level

This mode implies that no state associated with this data control has to be preserved to survive beyond the current HTTP request. This level is the most efficient in performance because there is no overhead related to state management. However, you should limit its use to applications that require no state management, or to cases when state no longer needs to be preserved at this point. Usually, you can programmatically release the application module with the unmanaged level when you want to signal that the user has ended a logical unit of work.

Performance Tip:

.The default release level is Managed, which implies that the application module's state is relevant and has to be preserved to allow the data control to span over several HTTP requests. Set release level to Unmanaged programmatically at runtime for particular pages to eliminate passivation and achieve better performance. A typical example is releasing the application module after servicing the HTTP request from a logout page.

To specify the unmanaged release level for an application module, set the release mode to Stateless.

49.2.2.3.3 About Reserved Release Level

This level guarantees that each data control will be assigned its own application module during its first request and for all subsequent requests coming from the HttpSession associated with this data control. This data control will always receive the same physical instance of application module. This mode exists for legacy compatibility reasons and for very rare special use cases.

An example of using Reserved level occurs when there is a pending database state across a request resulting from the postChanges() method or a PL/SQL stored procedure but not issuing a commit() or rollback() at the end of the request. In this case, if any other release level is used instead of Reserved, when the application module instance is recycled, a rollback is issued on the database connection associated with this application module instance and all uncommitted changes would be lost.

Performance Tip:

If you must use Reserved level, call setReleaseLevel() on the data control to keep its period as short as possible. For details about changing the release level programmatically, see Section 49.4, "Setting the Application Module Release Level at Runtime."

Consequences of Reserved mode can be adverse. Reliability suffers because if for whatever reason the application module is lost, the data control will not be able to receive any other application module in its place from the pool, and so HttpSession gets lost as well, which is not the case for managed level.

The failover option is ignored for an application module released with Reserved release level since its use implies your application absolutely requires working with the same application module instance on each request.

49.2.2.4 State Management and Subclassed Entity Objects

If your application employs subclassed entity objects, the key attribute of new entities must be prepopulated. If the key is not prepopulated, passivation and activation will fail. You can prepopulate the key attribute by overriding the create() method or by using the DBSequence type that will assign a temporary negative value to the key before the real value is fetched from the database after commit. For more information, see Section 4.10.10, "How to Get Trigger-Assigned Primary Key Values from a Database Sequence."

49.2.3 What You May Need to Know About Data Consistency and Application Module Affinity

When an entity object is updated concurrently by more than one user and one of those users loses affinity with their application module during the update, it is possible to experience data corruption if a change indicator has not been defined for the entity object.

Consider a scenario where two users (User A and User B) access the same entity object at the same time.

  • After querying, User B changes the value of an attribute (for example, changing the Dept attribute value from Accounting to Research) and then commits.

  • Meanwhile, User A has also queried and sees the same record where the value of the Dept attribute is Accounting.

  • User A loses affinity with the application module that was used to query, resulting in passivation.

  • User A then changes the Dept attribute value from Accounting to Sales and commits, resulting in activation.

  • When activation occurs, the new value of the Dept attribute is activated but the browser is unaware of the data change, and the commit proceeds without issue.

To avoid this type of scenario, define a change indicator attribute for all entity objects. For more information, see Section 4.10.11, "How to Protect Against Losing Simultaneously Updated Data."

49.2.4 What You May Need to Know About Using Application Scoped Managed Beans in a Clustered Environment

JSF Application Scope beans are backed by a servlet context which can only have one instance per cluster node. Therefore, for each design time configured bean, there will be a runtime instance of the bean for each node in a cluster. Because there is no synchronization across nodes, each instance of the application running on different nodes will have separate instances of that bean with separate values. Therefore, application scoped managed beans should not be used to carry application-wide values that can be updated to be shared across all nodes in a cluster. They can, however, be used to carry constants, read-only values, or values to reflect the state of a single node within the cluster (such as the number of users on a node).

49.3 Using Save For Later

To enable Save For Later, you must first add Save Points to the application at points where you would like application state and data to be preserved if the end user leaves the application. You can use it to save data and state information about a region, view port, or portlet. Later, you use the Save Point Restore activity to restore application state and data associated with a Save Point.

For more information on how create and restore Save Points, see Section 24.7, "Using Save Points in Task Flows."

Save For Later can also perform implicit saves. These occur when data is saved automatically without the end user performing an explicit Save action when the user session times out or closes the browser window, for example.

For more information on how to perform an implicit save, see Section 24.7, "Using Save Points in Task Flows."

49.4 Setting the Application Module Release Level at Runtime

If you do not want to use the default "Managed State" release level for application modules, you can set your desired level programmatically.

49.4.1 How to Set Unmanaged Level

To set a data control to release its application module using the unmanaged level, call the resetState() method on the DCDataControl class (in the oracle.adf.model.binding package).

You can call this method any time during the request. This will cause application module not to passivate its state at all when it is released to the pool at the end of the request. Note that this method only affects the current application module instance in the current request. After this, the application module is released in unmanaged level to the pool, it becomes unreferenced and gets reset. The next time the application module is used by a client, it will be used in the managed level again by default.

Note:

You can programmatically release the application module with the unmanaged level when you want to signal that the user has ended a logical unit of work. This will happen automatically when the HTTPSession times out, as described below.

49.4.2 How to Set Reserved Level

To set a data control to release its application module using the reserved level, call the setReleaseLevel() method of the DCJboDataControl class (in the oracle.adf.model.bc4j package), and pass the integer constant ApplicationModule.RELEASE_LEVEL_RESERVED.

When the release level for an application module has been changed to "Reserved" it will stay so for all subsequent requests until explicitly changed.

49.4.3 How to Set Managed Level

If you have set an application module to use reserved level, you can later set it back to use managed level by calling the setReleaseLevel() method of the DCJboDataControl class, and passing the integer constant ApplicationModule.RELEASE_LEVEL_MANAGED.

49.4.4 How to Set Release Level in a JSF Backing Bean

Example 49-1 shows calling the resetState() method on a data control named UserModuleDataControl from the action method of a JSF backing bean.

Example 49-1 Calling resetState() on Data Control in a JSF Backing Bean Action Method

package devguide.advanced.releasestateless.controller.backing;
import devguide.advanced.releasestateless.controller.JSFUtils;
import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCDataControl;
/**
 * JSF Backing bean for the "Example.jspx" page
 */
public class Example {
  /**
   * In an action method, call resetState() on the data control to cause
   * it to release to the pool with the "unmanaged" release level.
   * In other words, as a stateless application module.
   */
  public String button_action() {
    // Add event code here...
    getDataControl("UserModuleDataControl").resetState();
    return null;
  }
  private DCDataControl getDataControl(String name) {
    BindingContext bc = 
      (BindingContext)JSFUtils.resolveExpression("#{data}");
    return bc.findDataControl(name);
  }
}

49.4.5 How to Set Release Level in an ADF PagePhaseListener

Example 49-2 shows calling the resetState() method on a data control named UserModuleDataControl from the after-prepareRender phase of the ADF lifecycle using a custom ADF page phase-listener class. You would associate this custom class to a particular page by setting the ControllerClass attribute on the page's page definition to the fully qualified name of this class.

Example 49-2 Calling resetState() on Data Control in a Custom PagePhaseListener

package devguide.advanced.releasestateless.controller;
import oracle.adf.controller.v2.lifecycle.Lifecycle;
import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.controller.v2.lifecycle.PagePhaseListener;
import oracle.adf.model.binding.DCDataControl;
public class ReleaseStatelessPagePhaseListener
       implements PagePhaseListener {
  /**
   * In the "after" phase of the final "prepareRender" ADF Lifecycle
   * phase, call resetState() on the data control to cause it to release
   * to the pool with the "unmanaged" release level. In other words,
   * as a stateless application module.
   * 
   * @param event ADF page phase event
   */
  public void afterPhase(PagePhaseEvent event) {
    if (event.getPhaseId() == Lifecycle.PREPARE_RENDER_ID) {
      getDataControl("UserModuleDataControl", event).resetState();
    }
  }
  // Required to implement the PagePhaseListener interface
  public void beforePhase(PagePhaseEvent event) {}
  private DCDataControl getDataControl(String name,
                                       PagePhaseEvent event) {
    return event.getLifecycleContext()
                .getBindingContext()
                .findDataControl(name);
  }
}

49.4.6 How to Set Release Level in an ADF PageController

Example 49-3 shows calling the resetState() method on a data control named UserModuleDataControl from an overridden prepareRender() method of a custom ADF page controller class. You would associate this custom class to a particular page by setting the ControllerClass attribute on the page's page definition to the fully qualified name of this class.

Note:

You can accomplish basically the same kinds of page-specific lifecycle customization tasks using a custom PagePhaseListener or a custom PageController class. The key difference is that the PagePhaseListener interface can be implemented on any class, while a custom PageController must extend the PageController class in the oracle.adf.controller.v2.lifecycle package.

Example 49-3 Calling resetState() on Data Control in a Custom ADF PageController

package devguide.advanced.releasestateless.controller;
import oracle.adf.controller.v2.context.LifecycleContext;
import oracle.adf.controller.v2.lifecycle.PageController;
import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.model.binding.DCDataControl;
public class ReleaseStatelessPageController extends PageController {
  /**
   * After calling the super in the final prepareRender() phase
   * of the ADF Lifecycle, call resetState() on the data control
   * to cause it to release to the pool with the "unmanaged"
   * release level. In other words, as a stateless application module.
   * 
   * @param lcCtx ADF lifecycle context
   */
  public void prepareRender(LifecycleContext lcCtx) {
    super.prepareRender(lcCtx);
    getDataControl("UserModuleDataControl", lcCtx).resetState();    
  }
  private DCDataControl getDataControl(String name,
                                       LifecycleContext lcCtx) {
    return lcCtx.getBindingContext().findDataControl(name);
  }  
}

49.4.7 How to Set Release Level in a Custom ADF PageLifecycle

If you wanted to build a Fusion web application where every request was handled in a completely stateless way, use a global custom PageLifecycle class as shown in Example 49-4. For details on how to configure your application to use your custom lifecycle see Section 27.2, "About the JSF and ADF Page Lifecycles."

Example 49-4 Calling resetState() on Data Control in a Custom ADF PageLifecycle

package devguide.advanced.releasestateless.controller;
import oracle.adf.controller.faces.lifecycle.FacesPageLifecycle;
import oracle.adf.controller.v2.context.LifecycleContext;
import oracle.adf.model.binding.DCDataControl;
public class ReleaseStatelessPageLifecycle extends FacesPageLifecycle {
  /**
   * After calling the super in the final prepareRender() phase
   * of the ADF Lifecycle, call resetState() on the data control
   * to cause it to release to the pool with the "unmanaged"
   * release level. In other words, as a stateless application module.
   * 
   * @param lcCtx ADF lifecycle context
   */  
  public void prepareRender(LifecycleContext lcCtx) {
    super.prepareRender(lcCtx);
    getDataControl("UserModuleDataControl", lcCtx).resetState();    
  }
  private DCDataControl getDataControl(String name,
                                       LifecycleContext lcCtx) {
    return lcCtx.getBindingContext().findDataControl(name);
  }    
}

49.5 What Model State Is Saved and When It Is Cleaned Up

The information saved by application model passivation is divided in two parts: transactional and nontransactional state. Transactional state is the set of updates made to entity object data – performed either directly on entity objects or on entities through view object rows – that are intended to be saved into the database. Nontransactional state comprises view object runtime settings, such as the current row index, WHERE clause, and ORDER BY clause.

49.5.1 State Information Saved During Passivation

The information saved as part of the application module passivation snapshot includes the following.

Transactional State
  • New, modified, and deleted entities in the entity caches of the root application module for this user session's (including old/new values for modified ones).

Nontransactional State
  • For each active view object (both statically and dynamically created):

    • Current row indicator for each row set (typically one)

    • New rows and their positions. (New rows are treated differently then updated ones. Their index in the view object is traced as well.)

    • ViewCriteria and all related parameters such as view criteria row, etc.

    • Flag indicating whether or not a row set has been executed

    • Range start and Range size

    • Access mode

    • Fetch mode and fetch size

    • Any view object-level custom data

      Note:

      Transient view object attributes can be saved if they are selected for passivation at design time. However, use this feature judiciously because this results in a snapshot that will grow in size with the number of rows that have been retrieved.

    • SELECT, FROM, WHERE, and ORDER BY clause if created dynamically or changed from the View definition

Note:

If you enable ADF Business Components runtime diagnostics, the contents of each XML state snapshot are also saved. See Section 8.3.10, "How to Enable ADF Business Components Debug Diagnostics" for information on how to enable diagnostics.

49.5.2 Where the Model State Is Saved

By default, passivation snapshots are saved in the database, but you can configure it to use the file system as an alternative.

49.5.2.1 How Database-Backed Passivation Works

The passivated XML snapshot is written to a BLOB column in a table named PS_TXN, using a connection specified by the jbo.server.internal_connection property. Each time a passivation record is saved, it is assigned a unique passivation snapshot ID based on the sequence number taken from the PS_TXN_SEQ sequence. The ADF session cookie held by the application module data control in the ADF binding context remembers the latest passivation snapshot ID that was created on its behalf and remembers the previous ID that was used.

49.5.2.2 Controlling the Schema Where the State Management Table Resides

The ADF runtime recognizes a configuration property named jbo.server.internal_connection that controls which database connection and schema should be used for the creation of the PS_TXN table and the PS_TXN_SEQ sequence. If you don't set the value of this configuration parameter explicitly, then the state management facility creates the temporary tables using the credentials of the current application database connection.

To keep the temporary information separate, the state management facility uses a different connection instance from the database connection pool, but the database credentials are the same as the current user. Since the framework creates temporary tables, and possibly a sequence if they don't already exist, the implication of not setting a value for the jbo.server.internal_connection is that the current database user must have CREATE TABLE, CREATE INDEX and CREATE SEQUENCE privileges. Since this is often not desirable, Oracle recommends always supplying an appropriate value for the jbo.server.internal_connection property, providing the credentials for a state management schema where table and schema be created. Valid values for the jbo.server.internal_connection property in your configuration are:

  • A fully qualified JDBC connection URL like:

    jdbc:oracle:thin:username/password@host:port:SID

  • A JDBC datasource name like:

    java:/comp/env/jdbc/YourJavaEEDataSourceName

Performance Tip:

When creating the PS_TXN table, use securefiles to store LOB data (the content column), and create a primary column index on the PS_TXN table as global, partitioned reverse key index. The securefile configuration delivers superior performance over the basicfile configuration when working with LOB data. The reverse key index helps by reducing contention that can happen when the rate of inserts is high.

49.5.2.3 Configuring the Type of Passivation Store

Passivated information can be stored in several places. You can control it programmatically or by configuring an option in the application module configuration. The choices are database or a file stored on local file system:

  • File

    This choice may be the fastest available, because access to the file is faster then access to the database. This choice is good if the entire middle tier is either installed on the same machine or has access to a commonly shared file system, so passivated information is accessible to all. Usually, this choice may be good for a small middle tier where one Oracle WebLogic Server domain is used. In other words this is a very suitable choice for small middle tier such as one Oracle WebLogic Server instance with all its components installed on one physical machine. The location and name of the persistent snapshot files are determined by jbo.tmpdir property if specified. It follows usual rules of ADF property precedence for a configuration property. If nothing else is specified, then the location is determined by user.dir if specified. This is a default property and the property is OS specific.

  • Database

    This is the default choice. While it may be a little slower than passivating to file, it is by far the most reliable choice. With passivation to file, the common problem might be that it is not accessible to Oracle WebLogic Server instances that are remotely installed. In this case, in a cluster environment, if one node goes down the other may not be able to access passivated information and then failover will not work. Another possible problem is that even if file is accessible to the remote node, the access time for the local and remote node may be very different and performance will be inconsistent. With database access, time should be about the same for all nodes.

To set the value of your choice in design time, set the property jbo.passivationstore to database or file. The value null will indicate that a connection-type-specific default should be used. This will use database passivation for Oracle or DB2, and file serialization for any others.

To set the storage programmatically use the method setStoreForPassiveState() of interface oracle.jbo.ApplicationModule. The parameter values that you can pass are:

  • PASSIVATE_TO_DATABASE

  • PASSIVATE_TO_FILE

49.5.3 Cleaning Up the Model State

Under normal circumstances, the ADF state management facility provides automatic cleanup of the passivation snapshot records.

49.5.3.1 Previous Snapshot Removed When Next One Taken

When a passivation record is saved to the database on behalf of a session cookie, as described above, this passivation record gets a new, unique snapshot ID. The passivation record with the previous snapshot ID used by that same session cookie is deleted as part of the same transaction. In this way, assuming no server failures, there will only ever be a single passivation snapshot record per active end-user session.

49.5.3.2 Passivation Snapshot Removed on Unmanaged Release

The passivation snapshot record related to a session cookie is removed when the application module is checked into the pool with the unmanaged state level. This can occur when:

  • Your code specifically calls resetState() on the application module data control.

  • Your code explicitly invalidates the HttpSession, for example, as part of implementing an explicit "Logout" functionality.

  • The HttpSession times out due to exceeding the session timeout threshold for idle time and failover mode is disabled (which is the default).

In each of these cases, the application module pool also resets the application module referenced by the session cookie to be "unreferenced" again. Since no changes were ever saved into the underlying database tables, once the pending session state snapshots are removed, there remains no trace of the unfinished work the user session had completed up to that point.

49.5.3.3 Passivation Snapshot Retained in Failover Mode

When the failover mode is enabled, if the HttpSession times out due to session inactivity, then the passivation snapshot is retained so that the end user can resume work upon returning to the browser.

After a break in the action, when the end user returns to the browser and continues to use the application, it continues working as if nothing had changed. The session cookie is used to reactivate any available application module instance with the user's last pending state snapshot before handling the request. So, even though the users next request will be processed in the context of a new HttpSession (perhaps even in a different application server instance), the user is unaware that this has occurred.

Note:

If an application module was released with reserved level then the HttpSession times out, the user will have to go through authentication process, and all unsaved changes are lost.

49.5.4 Cleaning Up Temporary Storage Tables

JDeveloper supplies the adfbc_purge_statesnapshots.sql script to help with periodically cleaning up the application module state management table. You can find this file in the oracle_common subdirectory of your Oracle Middleware installation directory (for example, ORACLE_HOME\oracle_common\common\sql).

Persistent snapshot records can accumulate over time if the server has been shutdown in an abnormal way, such as might occur during development or due to a server failure. Running the script in SQL*Plus will create the BC4J_CLEANUP PL/SQL package. The two relevant procedures in this package are:

  • PROCEDURE Session_State(olderThan DATE)

    This procedure cleans up application module session state storage for sessions older than a given date.

  • PROCEDURE Session_State(olderThan_minutes INTEGER)

    This procedure cleans up application module session state storage for sessions older than a given number of minutes.

You can schedule periodic cleanup of your ADF temporary persistence storage by submitting an invocation of the appropriate procedure in this package as a database job.

You can use an anonymous PL/SQL block like the one shown in Example 49-5 to schedule the execution of bc4j_cleanup.session_state() to run starting tomorrow at 2:00 am and each day thereafter to cleanup sessions whose state is over 1 day (1440 minutes) old.

Example 49-5 Scheduling Periodic Cleanup of the State Management Table

SET SERVEROUTPUT ON
DECLARE
  jobId    BINARY_INTEGER;
  firstRun DATE;
BEGIN
  -- Start the job tomorrow at 2am
  firstRun := TO_DATE(TO_CHAR(SYSDATE+1,'DD-MON-YYYY')||' 02:00',
              'DD-MON-YYYY HH24:MI');
   -- Submit the job, indicating it should repeat once a day
  dbms_job.submit(job       => jobId,
                  -- Run the ADF Purge Script for Session State
                  -- to cleanup sessions older than 1 day (1440 minutes)
                  what      => 'bc4j_cleanup.session_state(1440);',
                  next_date => firstRun,
                  -- When completed, automatically reschedule
                  -- for 1 day later
                  interval  => 'SYSDATE + 1'
                 );
  dbms_output.put_line('Successfully submitted job. Job Id is '||jobId);
END;
.
/

49.6 Timing Out the HttpSession

Since HTTP is a stateless protocol, the server receives no implicit notice that a client has closed the browser or gone away for the weekend. Therefore any Java EE-compliant server provides a standard, configurable session timeout mechanism to allow resources tied to the HTTP session to be freed when the user has stopped performing requests. You can also programmatically force a timeout.

49.6.1 How to Configure the Implicit Timeout Due to User Inactivity

You configure the session timeout threshold using the <session-timeout> tag in the web.xml file. The default value is typically around 30 minutes, depending on the container. When the HttpSession times out the BindingContext goes out of scope, and along with it, any data controls that might have referenced application modules released to the pool in the managed state level. The application module pool resets any of these referenced application modules and marks the instances unreferenced again.

49.6.2 How to Code an Explicit HttpSession Timeout

To end a user's session before the session timeout expires, you can call the invalidate() method on the HttpSession object from a backing bean in response to the user's click on a Logout button or link. This cleans up the HttpSession in the same way as if the session time had expired. Using JSF and ADF, after invalidating the session, you must perform a redirect to the next page you want to display, rather than just doing a forward. Example 49-6 shows sample code to perform this task from a Logout button.

Example 49-6 Programatically Terminating a Session

public String logoutButton_action() throws IOException{
  ExternalContext ectx = FacesContext.getCurrentInstance().getExternalContext();
  HttpServletResponse response = (HttpServletResponse)ectx.getResponse();
  HttpSession session = (HttpSession)ectx.getSession(false);
  session.invalidate();
  response.sendRedirect("Welcome.jspx");
  return null;
}

As with the implicit timeouts, when the HTTP session is cleaned up this way, it ends up causing any referenced application modules to be marked unreferenced.

49.7 Managing Custom User-Specific Information

It is fairly common practice to add custom user-defined information in the application module in the form of member variables or some custom information stored in oracle.jbo.Session user data hashtable. The ADF state management facility provides a mechanism to save this custom information to the passivation snapshot as well, by overriding the passivateState() method and the activateState() methods in the ApplicationModuleImpl class.

Note:

Similar methods are available on the ViewObjectImpl class and the EntityObjectImpl class to save custom state for those objects to the passivation snapshot as well.

49.7.1 How to Passivate Custom User-Specific Information

You can override passivateState() and activateState() to ensure that custom application module state information is included in the passivation/activation cycle. Example 49-7 shows how this is done.

In the example, jbo.counter contains custom values you want to preserve across passivation and activation of the application module state. Each application module has an oracle.jbo.Session object associated with it that stores application module-specific session-level state. The session contains a user data hashtable where you can store transient information. For the user-specific data to "survive" across application module passivation and reactivation, you need to write code to save and restore this custom value into the application module state passivation snapshot.

Example 49-7 Passivating and Activating Custom Information in the State Snapshot XML Document

/**
 * Overridden framework method to passivate custom XML elements
 * into the pending state snapshot document
 */
public void passivateState(Document doc, Element parent) {
  // 1. Retrieve the value of the value to save
  int counterValue = getCounterValue();
  // 2. Create an XML element to contain the value
  Node node = doc.createElement(COUNTER);
  // 3. Create an XML text node to represent the value
  Node cNode = doc.createTextNode(Integer.toString(counterValue));
  // 4. Append the text node as a child of the element
  node.appendChild(cNode);
  // 5. Append the element to the parent element passed in
  parent.appendChild(node);
}
/**
 * Overridden framework method to activate  custom XML elements
 * into the pending state snapshot document
 */
public void activateState(Element elem) {
  super.activateState(elem);
  if (elem != null) {
    // 1. Search the element for any <jbo.counter> elements
    NodeList nl = elem.getElementsByTagName(COUNTER);
    if (nl != null) {
      // 2. If any found, loop over the nodes found
      for (int i=0, length = nl.getLength(); i < length; i++) {
        // 3. Get first child node of the <jbo.counter> element 
        Node child = nl.item(i).getFirstChild();
        if (child != null) {
          // 4. Set the counter value to the activated value
          setCounterValue(new Integer(child.getNodeValue()).intValue()+1);
          break;
        }
      }
    }
  }
}
/*
 * Helper Methods
 */
private int getCounterValue() {
  String counterValue = (String)getSession().getUserData().get(COUNTER);
  return counterValue == null ? 0 : Integer.parseInt(counterValue);
}
private void setCounterValue(int i) {
  getSession().getUserData().put(COUNTER,Integer.toString(i));
}
private static final String COUNTER = "jbo.counter";

49.7.2 What Happens When You Passivate Custom Information

In Example 49-7, when activateState() is overridden, the following steps are performed:

  1. Search the element for any jbo.counter elements.

  2. If any are found, loop over the nodes found in the node list.

  3. Get first child node of the jbo.counter element.

    It should be a DOM Text node whose value is the string you saved when your passivateState() method above got called, representing the value of the jbo.counter attribute.

  4. Set the counter value to the activated value from the snapshot.

When passivateState() is overridden, it performs the reverse job by doing the following:

  1. Retrieve the value of the value to save.

  2. Create an XML element to contain the value.

  3. Create an XML text node to represent the value.

  4. Append the text node as a child of the element.

  5. Append the element to the parent element passed in.

Note:

The API's used to manipulate nodes in an XML document are provided by the Document Object Model (DOM) interfaces in the org.w3c.dom package. These are part of the Java API for XML Processing (JAXP). See the Javadoc for the Node, Element, Text, Document, and NodeList interfaces in this package for more details.

49.7.3 What You May Need to Know About Activating Custom Information

The activateState() method is called at the end of the activation process after the view objects have been activated. Most of the time this is where you want to place the application module state activation logic. However, if your application module activation logic needs to set up custom state information before the ADF state management facility activates the view objects (for example, you might need to write custom code to allow the view objects to internally reference custom values at execution time), then the prepareForActivation() method in the ApplicationModuleImpl class would be the right place because it fires at the beginning of the activation process.

49.8 Managing the State of View Objects

By default, all view objects are marked as passivation-enabled, so their state will be saved. However, view objects that have transient attributes do not have those attributes passivated by default. You can change how a view object is passivated, and even which attributes are passivated, using the Tuning page of the view object overview editor.

49.8.1 How to Manage the State of View Objects

Each view object can be declaratively configured to be passivation-enabled or not. If a view object is not passivation enabled, then no information about it gets written in the application module passivation snapshot.

Performance Tip:

There is no need to passivate read-only view objects since they are not intended to be updated and are easily re-created from the XML definition. This eliminates the performance overhead associated with passivation and activation and reduces the CPU usage needed to maintain the application module pool.

Before you begin:

It may be helpful to have an understanding of passivation in view objects. For more information, see Section 49.8, "Managing the State of View Objects."

You will also need to perform the following task:

  • Open the application in JDeveloper.

To set the passivation state of a view object:

  1. In the Applications window, double-click the view object for which you want to set the passivation state.

  2. In the overview editor, click the General navigation tab.

  3. On the General page, expand the Tuning section and select Passivate State to make sure the view object data is saved.

  4. Optionally, you can select Including All Transient Attributes to passivate all transient attributes at this time, but see Section 49.8.4, "What You May Need to Know About Passivating Transient View Objects" for additional information.

49.8.2 What You May Need to Know About Passivating View Objects

The activation mechanism is designed to return your view object to the state it was in when the last passivation occurred. To ensure that, Oracle ADF stores in the state snapshot the values of any bind variables that were used for the last query execution. These bind variables are in addition to those that are set on the row set at the time of passivation. The passivated state also stores the user-supplied WHERE clause on the view object related to the row set at the time of passivation.

49.8.3 How to Manage the State of Transient View Objects and Attributes

Because view objects are marked as passivated by default, a transient view object — one that contains only transient attributes — is marked to be passivation enabled, but only passivates its information related to the current row and other nontransactional state.

Performance Tip:

Transient view object attributes are not passivated by default. Due to their nature, they are usually intended to be read-only and are easily re-created. So, it often doesn't make sense to passivate their values as part of the XML snapshot. This also avoids the performance overhead associated with passivation and activation and reduces the CPU usage needed to maintain the application module pool.

Before you begin:

It may be helpful to have an understanding of passivation in view objects. For more information, see Section 49.8, "Managing the State of View Objects."

You will also need to perform the following task:

  • Open the application in JDeveloper.

To individually set the passivation state for transient view object attributes:

  1. In the Applications window, double-click the view object that contains the transient attributes for which you want to specify the passivation state.

  2. In the overview editor, click the Attributes navigation tab.

  3. On the Attributes page, select the transient attribute you want to passivate and click the Details tab.

  4. In the Details page, select the Passivate checkbox.

49.8.4 What You May Need to Know About Passivating Transient View Objects

Passivating transient view object attributes is more costly resource-wise and performance- wise, because transactional functionality is usually managed on the entity object level. Since transient view objects are not based on an entity object, this means that all updates are managed in the view object row cache and not in the entity cache. Therefore, passivating transient view objects or transient view object attributes requires special runtime handling.

Usually passivation only saves the values that have been changed, but with transient view objects passivation has to save entire row. The row will include only the view object attributes marked for passivation.

49.8.5 How to Use Transient View Objects to Store Session-level Global Variables

Using passivation, you can use a view object to store one or more global variables, each on a different transient attribute. When you mark a transient attribute as passivated, the ADF Business Components framework will remember the transient values across passivation and activation in high-throughput and failover scenarios. Therefore, it is an easy way to implement a session-level global value that is backed up by the state management mechanism, instead of the less-efficient HTTP Session replication. This also makes it easy to bind to controls in the UI if necessary.

There are two basic approaches to store values between invocations of different screens, one is controller-centric, and the other is model-centric.

To implement this task in the ADF controller layer using the controller-centric approach involves storing and referencing values using attributes in the pageFlow scope. This approach might be appropriate if the global values do not need to be referenced internally by any implementations of ADF Business Components. For more information about pageFlow scope, see Section 20.2.4, "What You May Need to Know About Memory Scope for Task Flows."

The model-centric approach involves creating a transient view object, which is conceptually equivalent to a nondatabase block in Oracle Forms.

Before you begin:

It may be helpful to have an understanding of passivation in view objects. For more information, see Section 49.8, "Managing the State of View Objects."

You will also need to perform the following tasks:

  1. Open the application in JDeveloper.

  2. Create a new view object using the View Object wizard, as described in Section 5.2.1, "How to Create an Entity-Based View Object."

    1. On Page 1 of the wizard, select the option for Data Source: Programmatic.

    2. On Page 2, click New to define the transient attribute names and types the view object should contain.

    3. On Page 3, set the Updatable option to Always for each attribute.

    4. Click Finish and the newly created view object appears in the overview editor.

To store session-level global variables using the model-centric approach:

  1. Disable any entries from being performed in the view object:

    1. In the overview editor, on the General page, expand the Tuning section.

    2. In the Retrieve from the Database group, select the No Rows option.

  2. Make sure data in the view object is not cleared out during a rollback operation. To implement this, you enable a custom Java class for the view object and override two rollback methods.

    1. In the overview editor, click the Java navigation tab, and click the Edit icon for the Java Classes section.

    2. In the Select Java Options dialog, select Generate View Object Class and click OK.

    3. In the overview editor, click the link next to View Object Class in the Java Classes section.

    4. From the main menu, choose Source > Override Methods.

    5. In the Override Methods dialog, select the beforeRollback() and afterRollback() methods to override, and click then OK.

    6. In both the beforeRollback() and afterRollback() methods, comment out the call to super in the Java code.

  3. Add an instance of the transient view object to your application module's data model, as described in Section 13.2.3.2, "Adding Master-Detail View Object Instances to an Application Module."

  4. Create an empty row in the view object when a new user begins using the application module:

    1. In the Applications window, double-click the application module.

    2. Generate an application module Java class if you don't have one yet, as described in Section 13.7.1, "How to Generate a Custom Class for an Application Module."

    3. Override the prepareSession() method of the application module, as described in Section 13.11.1, "How to Override a Built-in Framework Method."

    4. After the call to super.prepareSession(), add code to create a new row in the transient view object and insert it into the view object.

Now you can bind read-only and updatable UI elements to the "global" view object attributes just as with any other view object using the Data Controls panel.

49.9 Using State Management for Middle-Tier Savepoints

In the database server you are likely familiar with the savepoint feature that allows a developer to rollback to a certain point within a transaction instead of rolling back the entire transaction. An application module offers the same feature but implemented in the middle tier.

Best Practice:

Oracle ADF provides a declarative approach to working with savepoints, described in Section 24.7, "Using Save Points in Task Flows." Use the programmatic approach described in Section 49.9.1, "How to Use State Management for Savepoints" only if the declarative approach doesn't meet your needs.

49.9.1 How to Use State Management for Savepoints

To use state management for implementing middle-tier savepoints, you override three methods in the oracle.jbo.ApplicationModule interface

public String passivateStateForUndo(String id,byte[] clientData,int flags)
public byte[] activateStateForUndo(String id,int flags)
public boolean isValidIdForUndo(String id)

You can use these methods to create a stack of named snapshots and restore the pending transaction state from them by name. Keep in mind that those snapshots do not survive past duration of transaction (for example, events of commit or rollback). This feature could be used to develop complex capabilities of the application, such as the ability to undo and redo changes. Another ambitious goal that could exploit this functionality would be functionality to make the browser back and forward buttons behave in an application-specific way. Otherwise, simple uses of these methods can come in quite handy.

49.10 Testing to Ensure Your Application Module is Activation-Safe

If you have not explicitly tested that your application module functions when its pending state gets activated from a passivation snapshot, then you may encounter an unpleasant surprise in your production environment when heavy system load tests this aspect of your system for the first time.

49.10.1 Understanding the jbo.ampool.doampooling Configuration Parameter

The jbo.ampool.doampooling configuration property corresponds to the Enable Application Module Pooling option in the Pooling and Scalability tab of the Edit Configuration dialog. By default, this checkbox is checked so that application module pooling is enabled. Whenever you deploy your application in a production environment the default setting of jbo.ampool.doampooling to true is the way you will run your application. But, as long as you run your application in a test environment, setting the property to false can play an important role in your testing. When this property is false, there is effectively no application pool. When the application module instance is released at the end of a request it is immediately removed. On subsequent requests made by the same user session, a new application module instance must be created to handle it and the pending state of the application module must be reactivated from the passivation store.

49.10.2 How To Disable Application Module Pooling to Test Activation

As part of your overall testing plan, you should adopt the practice of testing your application modules with the jbo.ampool.doampooling configuration parameter set to false. This setting completely disables application module pooling and forces the system to activate your application module's pending state from a passivation snapshot on each page request. It is an excellent way to detect problems that might occur in your production environment due to assumptions made in your custom application code.

Caution:

It is important to reenable application module pooling after you conclude testing and are ready to deploy the application to a production environment. The configuration property jbo.ampool.doampooling set to false is not a supported configuration for production applications and must be set to true before deploying the application.

For example, if you have transient view object attributes you believe should be getting passivated, this technique allows you to test that they are working as you expect. In addition, consider situations where you might have introduced:

  • Private member fields in application modules, view objects, or entity objects

  • Custom user session state in the Session user data hashtable

Your custom code likely assumes that this custom state will be maintained across HTTP requests. As long as you test with a single user on the JDeveloper Integrated WebLogic Server, or test with a small number of users, things will appear to work fine. This is due to the "stateless with affinity" optimization of the ADF application module pool. If system load allows, the pool will continue to return the same application module instance to a user on subsequent requests. However, under heavier load, during real-world use, it may not be able to achieve this optimization and will need to resort to grabbing any available application module instance and reactivating its pending state from a passivation snapshot. If you have not correctly overridden passivateState() and activateState() (as described in Section 49.7, "Managing Custom User-Specific Information") to save and reload your custom component state to the passivation snapshot, then your custom state will be missing (i.e. null or back to your default values) after this reactivation step. Testing with jbo.ampool.doampooling set to false allows you to quickly isolate these kinds of situations in your code.

49.11 Keeping Pending Changes in the Middle Tier

The ADF state management mechanism relies on passivation and activation to manage the state of an application module instance. Implementing this feature in a robust way is only possible if all pending changes are managed by the application module transaction in the middle tier. The most scalable strategy is to keep pending changes in middle-tier objects and not perform operations that cause pending database state to exist across HTTP requests. This allows the highest leverage of the performance optimizations offered by the application module pool and the most robust runtime behavior for your application.

Caution:

When the jbo.doconnectionpooling configuration parameter is set to true — typically in order to share a common pool of database connections across multiple application module pools — upon releasing your application module to the application module pool, its JDBC connection is released back to the database connection pool and a ROLLBACK will be issued on that connection. This implies that all changes which were posted but not commited will be lost. On the next request, when the application module is used, it will receive a JDBC connection from the pool, which may be a different JDBC connection instance from the one it used previously. Those changes that were posted to the database but not commited during the previous request are lost.

The jbo.doconnectionpooling configuration parameter is set by checking the Disconnect Application Module Upon Release property on the Pooling and Scalability tab of the Edit Configuration dialog.

49.11.1 How to Confirm That Applications Use Optimistic Locking

Oracle recommends using optimistic locking, the default mode for web applications. Pessimistic locking should not be used for web applications as it creates pending transactional state in the database in the form of row-level locks. If pessimistic locking is set, state management will work, but the locking mode will not perform as expected. Behind the scenes, every time an application module is recycled, a rollback is issued in the JDBC connection. This releases all the locks that pessimistic locking had created.

Performance Tip:

Always use the default mode optimistic locking for web applications. Only optimistic locking is compatible with the application module unmanaged release level mode, which allows the application module instance to be immediately released when a web page terminates. This provides the best level of performance for web applications that expect many users to access the application simultaneously.

To ensure your configuration uses optimistic locking, open the Properties tab of the Edit Configuration dialog and confirm that the value of the jbo.locking.mode property is set to optimistic or optupdate.

Note:

To open the Edit Configuration dialog, double-click the application module in the Applications window, and select the Configurations navigation tab from the overview editor. Then, in the Configurations page of the overview editor, select the configuration and click Edit.

Optimistic locking (optimistic) issues a SELECT FOR UPDATE statement to lock the row, then detects whether the row has been changed by another user by comparing the change indicator attribute — or, if no change indicator is specified, the values of all the persistent attributes of the current entity as they existed when the entity object was fetched into the cache.

Optimistic update locking (optupdate) does not perform any locking. The UPDATE statement determines whether the row was updated by another user by including a WHERE clause that will match the existing row to update only if the attribute values are unchanged since the current entity object was fetched.

49.11.2 How to Avoid Clashes Using the postChanges() Method

The transaction-level postChanges() method exists to force the transaction to post unvalidated changes without committing them. This method is not recommended for use in web applications unless you can guarantee that the transaction will definitely be committed or rolled-back during the same HTTP request. Failure to heed this advice can lead to strange results in an environment where both application modules and database connections can be pooled and shared serially by multiple different clients.

49.11.3 How to Use the Reserved Level For Pending Database States

If for some reason you need to create a transactional state in the database in a particular request by invoking the postChanges() method or by calling a PL/SQL stored procedure, but you cannot issue a commit or rollback by the end of that same request, then you must release the application module instance with the reserved level from that request until a subsequent request when you either commit or rollback.

Performance Tip:

Use as short a period of time as possible between creation of transactional state in the database and performing the concluding commit or rollback. This ensures that reserved level doesn't have to be used for a long time, as it has adverse effects on application's scalability and reliability.

Once an application module has been released with reserved level, it remains at that release level for all subsequent requests until release level is explicitly changed back to managed or unmanaged level. So, it is your responsibility to set release level back to managed level once commit or rollback has been issued.

For more information, see Section 49.4, "Setting the Application Module Release Level at Runtime."