40 Application State Management

This chapter describes the Fusion web application state management facilities and how to use them.

This chapter includes the following sections:

40.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.

40.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 his 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 abandoning 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.

40.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 40-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 he 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 40-1 Web Applications Use the Stateless HTTP Protocol

Web applications and stateless HTTP protocol

But even if you've never implemented your own web applications before, since you've undoubtedly used a web application to buy a book, plan a holiday, or even just read your email, it's clear that a solution must exist to distinguish one user from another.

40.1.3 How Cookies Are Used to Track a User Session

As shown in Figure 40-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.

A cookie that expires when you close your browser is known as a session cookie, while other cookies that are set to live beyond a single browser session might expire in a week, a month, or a year from when they were first created.

Figure 40-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 configured by the <session-timeout> element in the web.xml file. The default session length is 35 minutes.

40.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 40-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 40-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.

40.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 40.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.

40.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.

40.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 40-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 40-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 non-serializable object, you would see a compile-time error. If you were to try to replicate a session object that had non-serializable 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 40.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.

40.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 41.1, "Introduction to Application Module Pooling" and the discussion of Referenced Pool Size in Section 41.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 steps as in (2) above, 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.

40.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 Business Components 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.

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 41.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.

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.

40.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.
40.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.

This mode was called the "Stateful Release Mode" in previous releases of JDeveloper.

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 Business Components 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 40.10, "Testing to Ensure Your Application Module is Activation-Safe."
40.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.

This mode was called the "Stateless Release Mode" in previous releases of JDeveloper.

40.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 40.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.

40.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 18.7, "Adding Save Points to a Task Flow."

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 18.7, "Adding Save Points to a Task Flow."

40.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.

40.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.

40.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.

40.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.

40.4.4 How to Set Release Level in a JSF Backing Bean

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

Example 40-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 commandButton_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);
  }
}

40.4.5 How to Set Release Level in an ADF PagePhaseListener

Example 40-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 40-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);
  }
}

40.4.6 How to Set Release Level in an ADF PageController

Example 40-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 40-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);
  }  
}

40.4.7 How to Set Release Level in an 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 40-4. For details on how to configure your application to use your custom lifecycle see Section 21.2, "The JSF and ADF Page Lifecycles."

Example 40-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);
  }    
}

40.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 non-transactional 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. Non-transactional state comprises view object runtime settings, such as the current row index, WHERE clause, and ORDER BY clause.

40.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).

Non-Transactional 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 6.3.8, "How to Enable ADF Business Components Debug Diagnostics" for information on how to enable diagnostics.

40.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.

40.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.

40.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.

40.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 (one or multiple Oracle Application Server installation(s) and all their server instances) 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 Application Server is used. In other words this is a very suitable choice for small middle tier such as one Oracle Application Server 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 Application 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

40.5.3 Cleaning Up the Model State

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

40.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.

40.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.

40.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 his 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.

40.5.4 Cleaning Up Temporary Storage Tables

JDeveloper provides 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 procedures 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 40-5 to schedule the execution of bc4j_cleanup.session_state() to run starting tomorrow at 2:00am and each day thereafter to cleanup sessions whose state is over 1 day (1440 minutes) old.

Example 40-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 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;
.
/

40.6 Timing Out the HttpSession

Since HTTP is a stateless protocol, the server receives no implicit notice that a client has closed his 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.

40.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 35 minutes. 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.

40.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 40-6 shows sample code to perform this task from a Logout button.

Example 40-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.

40.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 either the activateState() method or the prepareForActivation() method 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.

40.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 40-7 shows how this is done.

Note:

The activateState() method is called at the end of the activation process after the view object 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 setup custom state information before the ADF statement 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 since it fires at the beginning of the activation process.

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 40-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";

40.7.2 What Happens When You Passivate Custom Information

In Example 40-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.

40.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.

40.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 recreated 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.

To set the passivation state of a view object:

  1. In the Application Navigator, double-click a view object to open it in the overview editor.

  2. On the General page, expand the Tuning section.

  3. Select Passivate State to make sure the view object data is saved.

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

40.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. This approach works for an application that does not dynamically reset the WHERE clause and bind variables on each request. In this case, the set of bind variable values used for the last executeQuery() and the set of bind variables current on the row set at passivation time are the same. The passivated state also stores the user-supplied WHERE clause on the view object related to the row set at the time of passivation.

However, when your application needs to dynamically change WHERE clauses and corresponding bind variables during the span of a single HTTP request, you need to ensure that the user-defined WHERE clause on the view object at the time of passivation matches the set of bind variable values used the last time the view object was executed before passivation. In this case, this is the correct sequence to follow:

  1. (Request begins and AM is acquired.)

  2. Call setWhereClause() on a view object instance that references n bind variables.

  3. Call setWhereClauseParam() to set the n values for those n bind variables.

  4. Call executeQuery().

  5. Call setWhereClause(null) to clear WHERE clause.

  6. Call setWhereClauseParams(null) to clear the WHERE clause bind variables.

  7. (AM is released.)

If you do not adhere to this strategy of changing runtime view object settings before using them, your application will fail with an SQL exception during application module pooling state activation:

JBO-27122: SQLStmtException: <... SQL Statement ...>

Because many of the view object's instance settings are saved in the passivation state snapshot and restored on activation (as described in Section 40.5.1, "State Information Saved During Passivation"), it is not advisable to change any of these settings just after executing the view object if you won't be re-executing the view object again during the same block of code (and so, during the same HTTP request). Instead, change the view object instance settings the next time you need them to be different before executing the query.

If you are dynamically adding named WHERE clause parameters to your view object instances, you might find it useful to add the following helper method to your ViewObjectImpl framework extension class. This method removes named bind variables that have been added to the view instance at runtime, without removing the ones that have been declaratively defined on the view definition at design time.

protected void clearWhereState() {
   ViewDefImpl viewDef = getViewDef();
   Variable[] viewInstanceVars = null;
   VariableManager viewInstanceVarMgr = ensureVariableManager();
   if (viewInstanceVarMgr != null) {
      viewInstanceVars = viewInstanceVarMgr.getVariablesOfKind(Variable.VAR_KIND_WHERE_CLAUSE_PARAM);
      if (viewInstanceVars != null) {
         for (Variable v: viewInstanceVars) {
            // only remove the variable if its not on the view def.
            if (!hasViewDefVariableNamed(v.getName())) {
              removeNamedWhereClauseParam(v.getName());
            }
         }
      }
   }
   getDefaultRowSet().setExecuteParameters(null, null, true);
   setWhereClause(null);
   getDefaultRowSet().setWhereClauseParams(null);
}
 
private boolean hasViewDefVariableNamed(String name) {
   boolean ret = false;
   VariableManager viewDefVarMgr = getViewDef().ensureVariableManager();
   if (viewDefVarMgr != null) {
      try {
         ret = viewDefVarMgr.findVariable(name) != null;
      }
      catch (NoDefException ex) {
         // ignore
      }
   }
   return ret;
}

40.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 non-transactional 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 recreated. 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.

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

  1. In the Application Navigator, double-click a view object to open it in the overview editor.

  2. On the Attributes page, select the transient attribute you want to passivate and click the Edit icon.

  3. In the Edit Attribute dialog, click the View Attribute node.

  4. Select the Passivate checkbox and click OK.

40.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.

40.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.

Implementation of the task in the ADF controller

The controller-centric approach involves storing and referencing values using attributes in the pageFlowScope. 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 14.2.4, "What You May Need to Know About Memory Scope for Task Flows."

Implementation of the task in the ADF model

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

  1. 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."

    • On step 1 of the wizard, select the option for Rows populated programmatically, not based on a query.

    • On step 2, click New to define the transient attribute names and types the view object should contain. Make sure to set the Updateable option to Always.

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

  2. Disable any queries from being performed in the view object.

    • On the General page of the overview editor, expand the Tuning section, and in the Retrieve from Database group box, select the No Rows option.

  3. 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.

    • On the Java page of the overview editor, click the Edit icon in the Java Classes section to open the Java dialog.

    • In the Java dialog, select Generate View Object Class and click OK.

    • In the overview editor, click on the hyperlink next to the View Object Class in the Java Classes section to open the source editor.

    • From the Source menu, choose Override Methods.

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

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

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

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

    • Enable a Java class for your application module if you don't have one yet.

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

    • 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 updateable UI elements to the "global" view object attributes just as with any other view object using the data control palette.

40.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 18.7, "Adding Save Points to a Task Flow." Use the programmatic approach described in Section 40.9.1, "How to Use State Management for Savepoints" only if the declarative approach doesn't meet your needs.

40.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 quite in handy.

40.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.

40.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 Business Components 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.

40.10.2 Disabling 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 40.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.

40.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.

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.

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 Business Components Configuration dialog.

40.11.1 How to Set Applications to Use Optimistic Locking

Oracle recommends using optimistic locking for web applications. Pessimistic locking, which is the default, 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:

Use 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 change your configuration to use optimistic locking, open the Properties tab of the Business Components Configuration dialog and set the value of the jbo.locking.mode to optimistic or optupdate.

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.

40.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.

40.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 some request by invoking postChanges() method or by calling 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 40.4, "Setting the Application Module Release Level at Runtime."