Oracle JDeveloper Tip

Understanding the ADF Business Components State Management Feature

Author: Michael Gantman, Oracle Worldwide Support
Contributions from John Smiljanic & Steve Muench, Oracle ADF Development Team

Date: October 6, 2004



NOTE:

This article contains advanced topics and it is assumed that reader has some experience and basic understanding of ADF Business Components (ADF BC).


Contents

Introduction
Overview
Review of Business Components Terminology
Review of HTTP Client to ADF BC Middle Tier Interaction
Review of State Management
Release Level
Setting the Release Level
Limitations Implied By the State Management Feature
Failover Feature
Passivation Storage Options
What Gets Passivated?
Timeouts
Managing Custom User Specific Info
Managing Transient ViewObjects
Partial Rollback on Middle Tier
Configuration Parameters and Interesting Use Cases
Testing ADF Business Components

Introduction

State management is a set of features in the Oracle Application Development Framework (ADF) that preserves the state of pending changes in your application over a period of time. This is a very typical task for a large group of applications called stateful web applications. Stateful web applications preserve logical application state across several HTTP requests. Web applications that do not preserve this kind of information are referred to as stateless applications. In stateless applications that involve database transactions, a transaction gets started and terminated by commit or rollback within the scope of one request. The reason for this is the stateless nature of the HTTP protocol itself. If a web application requires a transaction spanning multiple HTTP requests, it is the application’s responsibility to preserve the pending changes to be made as part of the transaction and to keep it consistent between HTTP requests. In this article, when we say “pending changes” or “pending session state” we mean the ADF entity objects that have been created, updated, or removed during the current transaction but which haven't yet been permanently stored to the database by a post or commit operation. The ADF Business Components features in ADF let your easily create stateful applications out of the box, with scalability nearing that of a purely stateless application. It is important for you to understand what is happening behind the scenes in order to make the most efficient use of this very productive feature.

Overview

Review of Business Components Terminology

An ADF BC middle tier application consists of one or more root Application Modules (AM). Each root AM correlates to a single database transaction. Each AM may contain instances of View Objects (VOs) and/or nested AMs (which always share the parent AM’s transaction). A VO’s attributes may be based on Entity Object (EO) attributes, be populated from a SQL-calculated expression (known as “SQL-derived”), or they can be calculated in Java and not correlate to a database at all (known as “transient”). By convention, if all attributes of a VO are transient then VO itself is called a transient VO.

Review of HTTP Client to ADF BC Middle Tier Interaction

Oracle ADF implements a MVC (Model – View – Controller) architecture. In this architecture the model is an abstraction level that separates client from middle tier business services. The client (known as the “View” in MVC) communicates with model layer through the controller, and the model layer relays the requests from client to the middle tier business services. The controller also relays the reply from the middle tier business services back to the model layer to that the View layer can display it to the client. In this way, the client layer is not dependent on the implementation details of your business services. Figure 1 shows schematically what is explained above.

Overview of MVC Architecture
Figure 1: Overview of MVC Architecture

It is important to understand the interaction between an HTTP client ( HttpSession) and the model layer. The ADFBindingFilter in the oracle.adf.model.servlet package is a servlet filter that intercepts all incoming requests from Web-based clients. On the first request the filter initializes the ADF data binding support classes. Among other things, during this bootstrap step it creates the BindingContext object and associates it with the HttpSession. There is one BindingContext object per HttpSession.

The BindingContext contains one or more Data Control and Binding Container objects. As its name implies, the Binding Container contains bindings, which are helper objects that abstract the details of “wiring” UI controls to business service data. These bindings interact with data provided and methods implemented by the Data Control in order to work with the model layer data. Each Data Control contains one ADF BC SessionCookie and uses it to checkout and release the underlying ApplicationModule instance that implements the business service’s functionality. That SessionCookie is used as an AM state identifier. An HttpSession can contain references to multiple Data Controls, but any given Data Control instance is always tied to one HttpSession. That is, the relationship between HttpSession and Data Controls is one to many. So in summary, the BindingContext is related to the HttpSession, and the Data Controls in the BindingContext use a SessionCookie to acquire and release an AM instance when they need to perform work. This SessionCookie is implemented by the oracle.jbo.http.HttpSessionCookieImpl class.

Now that we have the structure in place, we may describe in short how it is functioning. When the ADFBindingFilter receives an HTTP request it fires beginRequest notifications on all Data Controls that are associated with that request's HttpSession (via the BindingContext). It then passes the request to the next filter/servlet in the processing chain. Once the processing chain is finished control is passed back to the ADFBindingFilter. The ADFBindingFilter then fires endRequest notifications on all of the Data Controls that are associated with the request's HttpSession.

The ADF Business Components Data Control responds to the beginRequest notification by using the SessionCookie to acquire an ApplicationModule for the request. The ApplicationModule is therefore "pinned" to the Data Control for the duration of the request. The ADF BC Data Control responds to the endRequest notification by using the SessionCookie to release the request ApplicationModule. This article will discuss in depth the technologies provided that enable that release while still managing the Data Control state. Figure 2 illustrates the steps involved. This is of course a brief very short and incomplete description of how model layer works, but detailed discussion of this issue is out of scope of this document. We just need to establish general understanding for the purposes of discussion of several issues in this document later on.

Flow of Control During HTTP Request
Figure 2: Flow of Control During HTTP Request

Review of State Management

The following steps describe briefly how ADF uses state management to preserve pending changes made in the current HttpSession across HTTP requests. The simplest case is as follows:

  1. The Data Control (associated with HttpSession) requests an application module instance, and is given an unreferenced application module from a pool of instances. By unreferenced, we mean an application module that is not currently managing the pending state for any other user session)
  2. At the end of the request, the application module instance is checked back into the pool. The instance is now referenced by the Data Control that just used it. It 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 we’ll see below, it’s not dedicated to this Data Control, just referenced by it.
  3. The same Data Control (identified by its SessionCookie) makes a second request for an application module instance, and the pool supplies the same application module instance, with the state still saved in memory.

Sometimes application module instances need to be shared among different HttpSessions, because a high number of users are accessing the application simultaneously. In this case, the application pool must recycle a currently referenced application module instance for use by another session, as follows:

  1. A Data Control 1(associated with HttpSession 1) checks an AM instance (instance 1) back into the application pool, and the application state is saved in memory.
  2. Another Data Control (associated with HttpSession 2) requests an AM instance, and there are no unreferenced instances available in the pool. The AM instance then passivates its pending transaction state referenced by Data Control 1, by saving it to persistent storage, and resets its transaction state so it can be used by another Data Control. Instance 1 is now unreferenced, and the pool gives it to Data Control 2.
  3. Data Control 1 makes a second request for an AM instance. The pool activates state referenced by Data Control 1 by retrieving it from the persistent storage, assigning it to an AM instance (instance 2), and giving instance 2 to Data Control 1.

The process of passivation, activation, and recycling allows the state referenced by Data Control to be preserved across requests without requiring a dedicated AM 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, but as of yet uncommitted, changes. Options for persisting passivated state are described later in this document.


NOTE:

While ADF’s architecture allows any data control to participate in the state management lifecycle, currently only the ADF Business Components Data Control offers an out-of-the-box implementation of the state management support.


Release Level

Once a Data Control receives the “endRequest” notification (which means that HTTP request has been serviced and now is terminated) it releases and checks back the AM instance into the AM pool (or releases it for garbage collection if AM pool is not used). After every HTTP request Data Control is responsible for releasing the AM with the correct release level, one of the following three:

  • Unmanaged Level (formerly known as Stateless Mode)

    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, but it is limited in its use to applications that require no state management, or to cases when state no longer needs to be preserved at this point (classic example is releasing AM after servicing HTTP request from logout page.

  • Managed Level (formerly known as Stateful Mode)

    This is the default level. This level implies that AM’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 next request this Data Control will receive the same physical AM instance, but it guarantees that an AM with identical state will be provided so it is logically the same AM instance each time. It is important to note that the framework makes the best effort it can to provide the same instance of AM for the same Data Control if it is available at the moment. This is done for better performance since the same AM 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 AM that serviced that Data Control during previous is busy or unavailable different AM will activate this Data Control’s state. For this reason it is not valid to cache references to AM objects, View Objects, or View Rows across HTTP requests in controller-layer code.

  • Reserved Level (formerly known as Reserved Mode)

    This level guarantees that each Data Control will be assigned its own AM 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 AM. This mode exists for legacy compatibility reasons and for very rare special case uses. In general it is strongly recommended never to use this mode. The reasons are that with this mode since 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. Reliability suffers because if for whatever reason the AM is lost, the Data Control will not be able to receive any other AM in its place from the pool, and so HttpSession gets lost as well, which is not the case for managed level.


    NOTE: One case when Reserved mode may be used is when PL/SQL stored procedures are used, and so DB transaction state is created. (Or user must use postChanges method, which also will create DB transaction state. See further in the article details about postChanges method). In this case, because some state is stored in DB transaction the passivation/activation cycle will not be able to make a different AM instance identical to previous one. Thus, the Data Control has to use the same AM. However, this is a very special case, and general recommendation is to never use reserved level

Setting the Release Level

Release level can be set in your Struts action code related to the current request. If you are using an action that extends DataForwardAction in the oracle.adf.controller.struts.actions package, you will override the following method:

protected void handleLifecycle(DataActionContext actionContext) throws Exception

As mentioned above Managed Level is the default level and if request doesn’t take any action to override the specification of the release level, the Managed Level will be used. This is expected to be the common case. However, if the release level for AM has been changed to “Reserved” it will stay so for all consequent requests until explicitly changed. Therefore, it is possible to specify managed level explicitly as well. To do so invoke method:

setReleaseLevel(int releaseLevel)

of class oracle.adf.model.bc4j.DCJboDataControl or interface oracle.jbo.ApplicationModule (or override method getReleaseLevel()). Invoke the setter method with parameter ApplicationModule.RELEASE_LEVEL_MANAGED.

To set Unmanaged Level invoke the method resetState() of class DCDataControl (in the oracle.adf.model.binding package) at any time during request processing. This will cause AM not to passivate its state at all once it is released. (Obviously after this the AM will become unreferenced and reset, and when it will be used next time it will have its Release Level set to Stateful by default unless it is changed explicitly). To set Reserved Level use the setReleaseLevel() method in the class DCJboDataControl (in the oracle.adf.model.bc4j package) or on the oracle.jbo.ApplicationModule interface, passing the value ApplicationModule.RELEASE_LEVEL_RESERVED as an argument.

Limitations Implied By the State Management Feature

As explained above, the entire state management mechanism relies on passivation and activation as a vehicle to replicate the state of one AM instance to another one, so that those two instances are indistinguishable from the Data Control’s perspective. This is possible only if all pending changes are managed by the application module instance on the middle tier. This insures that each passivated “snapshot” of the pending changes accurately reflects all the changes the user has made but not yet committed. The alternative would be to save the pending changes to the database by posting the appropriate INSERT, UPDATE, and DELETE statements in the current database transaction – without committing them yet – however this approach would have two major drawbacks, one on performance and one on functionality.

Before explaining the details of those drawbacks it is important to mention that by default AM is configured to use AM pooling and not to use supplementary connection pooling. What this means is that AM receives its JDBC connection at the moment it is created and holds this connection for its entire lifespan in the pool. So by default AM is always tied to the same JDBC connection. It is recommended to keep such configuration as it is the best choice for performance. This is so because by holding onto the JDBC connection that was used to create it’s JDBC PreparedStatement objects, ADF can reuse those. With this in mind we now will explain what the drawbacks on performance and functionality are if pending changes are posted to Database but not committed.

Regarding performance, the AM would be forced to stay dedicated to its current Data Control to remain in the context of these pending changes. This would effectively require releasing the application module with the Reserved Level and would lower application scalability. Regarding functionality, doing early posting of changes to the database would force possibly incomplete information to be posted to the database before they were completely valid, potentially causing integrity violations. The cleanest and most scalable strategy is to keep pending changes in middle-tier objects and not tie Data Control to a particular AM instance and its JDBC connection containing pending database-level transaction state. This allows the highest leverage of the performance optimizations offered by the Application Module pool.

So, based on the above the following restrictions apply when using State persistence spanning over several HTTP requests:

  • Method postChanges should not be used because it posts the changes into the transaction and thus creates transactional state. Note that there is no mechanism to prevent developers from invoking postChanges method, but if it is invoked it will compromise the AM state and will lead to unpredictable and possibly unrecoverable negative results.
  • Pessimistic Locking should not be used as it creates locks in the database, which is also a transactional state. If pessimistic locking is set, state management will work, but the locking mode will not perform as expected. Behind the scenes, every time an AM is recycled, a rollback is issued in the JDBC connection. This releases all the locks that pessimistic locking has created. We recommend using Optimistic Locking. Note that by default locking mode is set to “Pessimistic”. To change it to Optimistic open AM configuration panel (by right clicking on AM and choosing option “Configurations…”) click on edit button and go to “Properties” tab. There change the value of parameter jbo.locking.mode to “optimistic”.

If you do want to create a transactional state in Database in some request by invoking postChanges() method or by calling PL/SQL stored procedure but do not want to issue commit or rollback by the end of request, the solution is that from that request on and until commit or rollback is issued all requests for this Data Control must release AM with reserved level, guaranteeing that the HttpSession will use the same AM instance. The recommendation is that it should be a short period of time between creation of transactional state in Database and commit or rollback, so reserved level doesn’t have to be used for long time. Because, as mentioned above, reserved level has adverse effects on application’s scalability and reliability.


NOTE:

Once an application module has been released with Reserved Level, it will be configured to be released with that level for all subsequent requests until release level is explicitly changed. So it is the developer’s responsibility to set Release Level back to Managed Level (Stateful Mode) once commit or rollback has been issued.


The statement above brings us to another best practice :

The use of database connection pooling (parameter jbo.doconnectionpooling set to true) fundamentally incompatible with the practice of calling postChanges() and/or invoking PL/SQL stored procedures which perform DML operations in the current transaction —without committing the transaction in the same request.

As explained above, if you either:

  • call the postChanges() method, or
  • invoke custom PL/SQL stored procedures which perform DML statements

outside of the normal framework transaction commit processing cycle, then this requires releasing your application module with the Reserved Level so it stays dedicated to its current Data Control and its underlying application module instance in which this pending database transaction state was first created.

However, if you are using the Disconnect Application Module Upon Release option in the application module's configuration in order to share a common pool of database connections across multiple application module pools, then upon releasing your application module to the AM pool, its JDBC connection will be disconnected from it and 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 AM is used it will receive a JDBC connection from the pool — which likely won't be the exact same connection object that was used previously — those changes that were posted but not commited into DB during previous request are no longer there.

Failover Feature

The ADF Business Components 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 of affairs” from a persisted snapshot.

There is a parameter called jbo.dofailover that can be set in your AM configuration. This parameter controls when and how often passivation occurs. If the failover feature is turned off, then application module pending state will only be passivated just before the pool must hand out a currently-referenced AM instance to a different Data Control. That is, the AM state is not saved until it has to be. With failover feature turned on, the application module’s pending state is passivated every time it is checked in back into AM pool thus saving its state immediately upon return to the pool.

The idea behind failover is that if for whatever reason AM instance gets lost (for example one of OC4J instances to which this application is deployed crashed) its state is always saved and may be activated by any AM instance at any time. This capability comes at expense of the additional overhead of performing passivation eagerly every time AM is checked in (with managed level) back into the pool. The failover option is a classic case of performance versus reliability trade off. There is no clear-cut recommendation on the value of this parameter and each user has to make the decision based on the resources available and the nature of the application. The default setting of the failover mode is true; out of the box ADF BC opts to err on the side of reliability, rather than performance in this area.


NOTE:

The failover option is ignored for AM released with Reserved release level since the meaning of Reserved level is that Data Control requires the same AM and substitute by different AM is not acceptable, making the failover option meaningless in this case. But as soon as release level will be changed from Reserved back to Stateful the value of the failover parameter will become relevant.


Passivation Storage Options

Passivated information can be stored in several places. You can control it by configuring an option in AM configuration or programmatically. The choices are Database, file stored on local file system and memory:

  • File

    This choice may be the fastest available as 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 OC4J instances either installed on the same machine or have access to commonly shared file system, so passivated information is accessible to all. Usually, this choice may be good for small middle tier where one OracleAS is used. In other words this is very suitable choice for small middle tier such as one Oracle AS 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 BC4J property precedence for a configuration property. If nothing else is specified then the location is determined by user.dir if specified as a java.util.Property. This is a default property and OS specific.

  • Database

    This is the default choice. While performance-wise it may be a little slower then 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 OracleAS instances that are remotely installed, so 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 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. So database is the most reliable and robust choice even though somewhat slower then file. Passivated information is stored as XML snapshot (this is true for all storages), and in case of Database storage it is written into BLOB column into temporary table within schema specified in jbo.server.internal_connection property

  • Memory

    This choice is for testing purposes only and should not be used in production environment. An obvious problem is that this storage will not be accessible to different processes and nodes.

To set the value of your choice in design time set the property jbo.passivationstore to database or file. Value null will indicate that default should be used, which is database. Any values other than "database" and "file" will result default behaviour. Specifically, a DBSerializer is used by default if the database type is Oracle or DB2. A FileSerializer is used by default otherwise.

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
  • PASSIVATE_TO_MEMORY

What Gets Passivated?

Here is a partial list of the information passivated during as part of the AM passivation “snapshot”.

  • 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)
  • And for each active view object (both statically and dynamically created)

    • Current row indicator for each rowset (typically one)
    • New rows and their positions (New rows are treated differently then updated ones. Their index in the VO is traced as well)
    • ViewCriteria and all its related parameters such as view criteria row etc.
    • Flag indicating whether or not a rowset has been executed
    • Range start and Range size
    • Access mode
    • Fetch mode and fetch size
    • Any VO-level custom data
    • SELECT, FROM, WHERE, and ORDER BY clause if created dynamically or changed from the View definition

NOTE:

This is not a full list of what is passivated but covers the bulk of what's written out.


Here is simple example of a passivation snapshot from AM that holds VO based on Departments table from HR schema. This snapshot is holding one new row with department number value 271 and department name value “TestDept” :

<AM MomVer="0">
  <cd/>
  <TXN Def="1" New="0" Lok="1">
    <EO Name="model.Departments">
      <![CDATA[000100000003C20348]]>
      <DepartmentsRow PS="0" PK="Y">
        <DepartmentId>271</DepartmentId>
        <DepartmentName>TestDept</DepartmentName>
      </DepartmentsRow>
    </EO>
  </TXN>
  <VO>
    <VO It="1" Sz="10" St="17" Ex="1" Def="model.DepartmentsView"
        Name="DepartmentsView1" cli="1">
      <Key>
        <![CDATA[000100000003C20348]]>
      </Key>
      <cd>
        <QC>
          <Ke>ACED000577020000</Ke>
          <NR Hdl="76" Idx="26">
            <Ke>000100000003C20348</Ke>
          </NR>
        </QC>
      </cd>
    </VO>
    <VO It="1" Sz="10" Ex="1" Def="model.EmployeesView"
        Name="EmployeesView3" cli="1"/>
  </VO>
</AM>

Timeouts

There are several timeout parameters that can be configured. Two of them will be discussed in this article. First one is HTTP timeout. This parameter is not specified in AM configuration. It is specified in your client project for web application in file web.xml. The default value is 35 minutes. It could be edited manually in text of web.xml file and finding the following fragment:

<session-config>
  <session-timeout>35</session-timeout>
</session-config>

Or better yet, by making a right click on the file and choosing Properties... . The HttpSession timeout titled “ Session timeout ” may be specified in General section (the first one that appears by default).

However, the most interesting thing about this parameter is not how to set it but how it is interpreted by the middle tier J2EE container. The HttpSession may be treated as physical resource or as logical user session. Logical user session means a user interaction session with application. Physical resource means that HttpSession is treated as some memory-consuming object that may be removed and reinstantiated according to system needs and its termination and reinstantiation is completely transparent to the user.

BC4J uses the SessionCookie stored in Data Control to store a state identifier for AM state related to Binding Context that correlated to that HttpSession (the passivation id). Recall that the HttpSession has a single BindingContext and all Data Controls in the BindingContext are indirectly related to the same HttpSession as containees of BindingContext. When the HttpSession times out the BindingContext will go out of scope, so that SessionCookie stored in the Data Control and used as state identifier will go out of scope and the AM state referenced by this Data Control may no longer be accessible. At the same time AM holding current state will become unreferenced and it will be reset (its state cleaned up) and available to service any HttpSession. If failover is disabled the HttpSession timeout will represent both the physical and the logical end of that session because both the physical HttpSession instance and the state associated with that session will be inaccessible. (So in this case user attempting to continue working will not be able to do so until (s)he starts a new HttpSession. The old HttpSession and state associated with it are discarded. If failover is enabled, the HttpSession timeout will represent the physical end of the session only; the HttpSession instance for that session will be inaccessible but the state associated with Data Control related to that session will still be accessible. (Even though BindingContext with all Data Controls it contains will be out of scope together with its HttpSession). At this point the AM also will be reset and made unreferenced but its state is passivated earlier due to enabled failover feature. The reason that the state still will be accessible is because when failover is enabled the HttpSession cookie that is stored in the HttpSession will also be stored on the browser tier as a browser cookie. This is performed by the framework to ensure that the possibility of re-establishing the link between a new HttpSession and the pending session state in the case of being routed to a different middle-tier server.

For example, consider a scenario in which a user opens the browser does some work and lets say does not commit his changes and leaves for lunch. Assume that failover is enabled. The HttpSession will time out and will be removed from the memory and so will BindingContext. Corresponding AM will become unreferenced and it will be reset, but it will not signify the end of user’s session from user’s point of view. When user comes back and tries to continue working, his HttpSession is recreated using browser cookie that is stored on his local file system, ADFBindingFilter will perform bootstraping again and will recreate the BindingContext with its Data Controls with SessionCookie holding identical state identifier, and Data Control that checked out AM during first request from this HttpSession will be able to identify and activate the AM state referenced by the previous HttpSession through its old Data Control. So, the user will be able to continue working as if the HttpSession had never timed out. In other words, from user’s point of view the session never was terminated. The session is treated only as physical resource that may be removed for efficiency reasons but the logical application session lives on, so to speak.

To test this, try the following: create simple ADF Business Components application and web client to it. Set the HttpSession timeout to 1 minute, and make sure that failover is enabled, run your client application, create a new row or modify some value but do not commit (or rollback). Now wait for more then one minute to let the HttpSession timeout. Note that if in your ViewController project you add a Java VM property on the “Runner” panel like -Djbo.debugoutput=console you will actually see when your HttpSession times out in the debug trace information. Now try to continue working. You will see that all your state has been preserved as if the HttpSession never timed out. You will be able to see all your changes that have not been committed yet and you still will be able to commit them as well. Now you may try the same scenario, but now with failover turned off. You will see that after timeout your pending state is lost. So you will have to start from the point where you last committed your changes.

So, if from user’s perspective HTTP timeout is transparent and unnoticeable what is it really used for? It is used for resource management. With Internet Explorer it is possible to launch the browser several times from the (Start) menu, and each gets run in a separate process and behaves as if it were a separate browser user.


NOTE:

In contrast, other browsers — including IE itself if you only create a new browser window rather than launching another browser process — treat all windows as the same browser user.


Assuming that user may open many browser windows, it's clearly the case that they can only perform active work in one window at a time. So you specify HttpSession timeout so inactive sessions will be removed and AMs referenced by them become unreferenced and will be reset to save the resources and they will be restored only when someone will continue to work on particular window. However, be careful not to set it too short so excessive removal and recreating of HttpSessions and extra passivation and activation of AMs do not occur.

One more detail about HttpSession timeout is that it could be caused programmatically. To do so user needs to get access to instance of the class javax.servlet.http.HttpSession and to invoke its method called invalidate(). Note that this will have the same effect as HTTP timeout, which does not necessarily mean logical session termination as described above.

In many cases it important that unauthorized user may not get an access to an existing HttpSession after an authorized user has finished working on his/her session. To prevent this, logical user’s session has to be terminated, and that as we saw does not happen with HttpSession timeout if failover is enabled. One way to do so is to close the browser window (or more precisely all windows belonging to the same browser process). This will terminate the process and effectively will end user’s HttpSession. But even that could be reconfigured, as it will be explained below. Using another timeout parameter that we will discuss later, it is possible to make the logical session survive for a certain time even after browser process has been terminated. The other way to properly terminate user’s session is to create a button or link in the application called “Log off” and upon user clicking on this button AM should be released with stateless level. This will cause AM not to get passivated at all, so no state for this session will be saved. (Of course it is recommended that at this moment all pending transaction changes have been committed or rolled back. As the developer, you may want to check to make sure this is so, and if not appropriate action should be taken before log-off occurs).

There is a very important exception to the rule that the HttpSession is treated as physical resource only and not the logical termination of user’s session when failover is enabled. If AM was released with reserved level the HttpSession timeout will be logical session termination and user will not be able to continue working after timeout as if nothing happened. User will have to go through authentication process, and all unsaved changes will be lost. Recall that reserved release level ignores the jbo.dofailover value. Of course, if failover is disabled then HttpSession will be treated as logical termination of user’s session regardless of release level.

There’s something important to understand about the HttpSession object. ADF Business Components session state is identified by an ADF BC SessionCookie. The ADF Business Components SessionCookie registers itself with the HttpSession so that it may be managed between the requests of an HttpSession. An HttpSession is identified by an id generated by the servlet container. An application developer may use a variety of HttpSession tracking mechanisms, like browser cookies and URL rewriting, to associate the HttpSession id with a browser and to correlate a set of HTTP requests from a browser with a particular HttpSession.

The SessionCookie in turn has a browser cookie stored on the local file system where the browser is located. If web application is marked as distributed and it is installed on several nodes within a cluster, at run time the HttpSession and its SessionCookie is replicated to all instances of OC4J where application is installed. (This is true only if application is configured to be in state replicating mode on OracleAS). So if one of the nodes goes down HttpSession still will be available on other OC4J instances for seamless failover. If replication fails, the ADF BC SessionCookie can still be recreated; the generic HttpSession may not be recreated. This means the logical user session and their pending work can still can be recovered and get recreated using the browser cookie.

The second important timeout parameter that we will discuss is set in AM configuration and it is called jbo.maxpoolcookieage. This parameter is specified in seconds and it defines how long browser cookie will survive after the browser process has been terminated or after the last activity in the browser. The default value is –1 and for most of the cases it should always remain such. The default value means that browser cookie will exist as long as browser process is alive. If you set it to positive value it will mean that browser cookie will be kept for specified period of time after last activity in the browser, even if the browser process is terminated. The most important consequence that needs to be understood here is, that if user opens the browser and creates some AM state within his application and then closes all browser windows, if the same browser will be reopened within the timeout period (one that is specified in jbo.maxpoolcookieage parameter), user’s session will be restored and all the uncommited changes will be restored with it. That is, the user may continue working as if the browser’s process had never been terminated.


NOTE:

Due to known bug in JDeveloper version 9.0.5.X the parameter jbo.maxpoolcookieage is ignored if set in AM configuration and default is always used. To work around this problem this parameter could be passed as command line parameter in your client project properties. (It has to be set in client project as oppose to server one since it is client project that is executed and it is its properties that are picked up) In project properties under “Runner” you may specify additional parameter in java options. To pass the options use –D flag. For instance for this parameter you will need to add -Djbo.maxpoolcookieage=300. This will set the value of this parameter to 5 minutes. (There is another parameter that gets ignored in AM configuration and it is jbo.ampool.monitorsleepinterval. This is also a known bug. This parameter is not discussed in this document). Both of those mentioned bugs are fixed in JDeveloper 10.1.2 and from that version on there will be no need to set any parameters in this way, although it still will be possible to do so.


I’ll use a simple use case to demonstrate the point above. Set the value jbo.maxpoolcookieage property to 5 minutes, run your Web application and create some state by updating or inserting some record. Now close the browser. (Make sure that your browser process has been terminated by checking that in your task manager browser process is no longer found). Open the browser again within time frame not longer then timeout you specified. Notice that your changes are picked up and you continue working as if browser was never closed.

So going back to HttpSession timeout interpreted as physical resource and not logical session termination for stateful release level with failover enabled: if developer wants to change this behavior and make HttpSession timeout to be interpreted as logical session termination, developer may set HttpSession timeout and jbo.maxpoolcookieage to the same value. In this case both HttpSession cookie and ADF BC SessionCookie-related browser cookie will go out of scope and will be deleted at the same time and there will be nothing left to restore session from. A drawback is that the ADF BC session state could be restored after the browser process has been killed if that browser is restarted before the jbo.maxpoolcookieage has elapsed. For example, if one user performed some work and closed the browser then a second user could restart a new browser instance on that machine and gain access to any AM/VO/Transaction state (where clauses, RowSets, etc.) that was created by the first user.


NOTE:

Most of the users will never need to change the default value for this property. Don’t change this value if you don’t absolutely need it. Changing the default to some positive value creates a potential security vulnerability. If you change this value make sure that all consequences are anticipated.


Managing Custom User Specific Info

It is fairly common practice to add custom user defined information in the AM. It could be member variables added by user into AM or some custom information stored in session object. ADF BC framework provides mechanism to passivate this information as well. This is done using so-called hooks – API provides some empty methods that are invoked by framework during passivation and activation. By overriding those methods user gets the chance to add custom logic executed with each passivation and activation occurrence. The methods that user may override to take advantage of this feature could be found in oracle.jbo.server.ApplicationModuleImpl. Some of the methods are:

protected void passivateState(Document doc, Element parent)
public void activateState(Element elem)

There are other hooks that could be used for this purpose as well. Please see API for oracle.jbo.ApplicationModule and oracle.jbo.server.ApplicationModuleImpl for more details. VO and EO also have hook methods. Below is a simple example that shows how to override methods mentioned above to make sure that custom information stored in AM is included in passivation/activation cycle.

Assume that you would like to keep some custom parameter in your AM called jbo.counter that has session related value that you are interested to preserve. Each AM has a session object associated with it that stores its state (please do not confuse with HTTPSession, See oracle.jbo.Session interface for reference) in the session there is an object designated for storing user custom information. It is called “User’s data”. This is where custom info usually stored. So in this case in your class that extends oracle.jbo.server.ApplicationModuleImpl you will need to override two methods mentioned above. It might look like this:

public void passivateState(Document doc, Element parent) {
  int counterValue = getCounterValue();
  Node node = doc.createElement(COUNTER);
  Node cNode = doc.createTextNode(Integer.toString(counterValue));
  node.appendChild(cNode);
  parent.appendChild(node);
}
public void activateState(Element elem) {
  super.activateState(elem);
  if (elem != null) {
    NodeList nl = elem.getElementsByTagName(COUNTER);
    if (nl != null) {
      for (int i=0, length = nl.getLength(); i < length; i++) {
        Node child = nl.item(i).getFirstChild();
        if (child != null) {
          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";

This is a simple example that demonstrates use of some but not all hooks that allow developer to take care of persisting custom information.

Managing Transient ViewObjects

For passivation/activation purposes Transient VO attributes and VO attributes not based on EO ones but derived directly from the database through SQL-calculated expressions are treated in the same way. So for simplicity only transient versus EO based attributes are discussed here. Transient attributes are not passivated by default. This is because due to their nature they are usually intended to be “read only” and very easily recreatable. So it often doesn’t make sense to passivate their values as part of the XML snapshot. However, each transient attribute may be configured to be passivation-enabled. And every VO can be also configured to be passivation enabled. This is true for any VO and not only for transient VO. By default all VOs are marked as passivation-enabled, and all transient attributes are not. That means that VO that only contains transient attributes is marked to be passivation enabled, but only passivates its non transactional state.

Recall that information saved by passivation is divided in two parts: transactional and non-transactional state. Transactional state is the set of updates made to EO data – performed either directly on entity objects or on entities through view object rows – that are intended to be saved into DB. Non-transactional state comprises VO attributes, such as current row index, “where” clause, “order by” clause etc. Please see the section “What gets passivated” for details.

It is worth to mention that passivating transient attributes is more costly resource- and performance- wise, because transactional functionality is usually managed on EO level. Since transient VOs are not based on EO this means that all updates are managed in VO cache and not in EO as usual. Therefore passivating transient VOs or attributes requires special handling. Usually passivation only saves the values that have been changed, but with Transient VOs passivation has to save entire row (row will include only attributes marked for passivation).

Consider the following example: regular (i.e. non-transient) VO contains 10 rows and each row has 7 attributes. The update has been made in two attributes in row number 2 and one attribute in row number 5. During the passivation three values will be passivated (the ones that have been updated). Now consider the same size transient VO with 4 out of 7 transient attributes marked for passivation. Assume that the same updates where made as in previous case for regular VO. The passivation will have to save all 10 rows with 4 attributes each (as we said that only 4 attributes out of 7 are marked for passivation). This is 40 attributes to passivate as oppose to 3 in case of regular VO. Therefore if you have any transient VOs or attributes that you want to passivate, consider making them not transient in the first place.

To configure VO to be passivation-enabled or disabled, double-click on VO to open the VO Editor and go to the Tuning panel. There is a checkbox there called “Enable passivation” that is set on by default. There is additional checkbox there called “For all transient attributes”, and this box is not checked on by default. Checking this box will mark all transient attributes to be passivation-enabled. It is also possible to mark each transient attribute as passivation enabled. In the VO editor, go to the Attributes node in the left-hand tree and expand it to show each attribute. For each attribute you can set attribute related configuration options. Note that checkbox called “Passivate” appears for transient attributes only.

There is no need for any custom code to passivate transient attributes. But keep in mind that it is costly operation and use it sparingly.

Partial Rollback on Middle Tier

Partial rollback is a potentially very powerful feature. In the database server we have the savepoint feature that allows a developer to rollback to a certain point within a transaction instead rolling back the entire transaction. Here we have the same feature but implemented in middle tier. There are three methods in oracle.jbo.ApplicationModule interface that allow you to take advantage of this feature. The methods are:

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

These methods allow developer to create a stack of identifiable snapshots and restore the pending transaction state from them by name. Keep in mind that those snapshots do not survive past duration of transaction i.e. events of commit or rollback. This feature could be used to develop complex capability of application to undo and redo changes. One of ambitious goals could be implement functionality for back and forward browser buttons. But more simple uses obviously could be very handy.

Configuration Parameters and Interesting Use Cases

Tuning ADF-BC is not the topic of this paper, but there are some very important parameters that should be mentioned here, because they have strong impact on state management. Also some simple testing scenarios that should help to demonstrate visually some complicated issues explained in this paper.

The first parameter is jbo.recyclethreshhold. This parameter controls how many AM instances AM pool tries to preserve to be “sticky” to their Data Controls before it will take one to recycle instead of creating a new AM instance. The value of this parameter should be equal to the expected number of "active" concurrent HTTP sessions. Define an "active" session as one, which will be expected to send subsequent requests before timing out. If this parameter is lower then it should be it will cause excessive recycling of AMs which will have negative impact on performance.

jbo.ampool.maxavailablesize - This is the maximal amount of Application modules that could be kept idle in the pool waiting for the user to come. If one available then upon request it will be given instead of creating a new one, which is a costly performance operation. Creation of new AM involves creating AM instance itself and all its VOs as well. When each VO is instantiated the VO’s query gets executed. So each AM creation involves execution of multiple queries (one pr each VO). Therefore it is best to configure AM pool to have minimal amount of creations and removals of AMs

jbo.ampool.minavailablesize - This is the minimal amount of Application modules that will be kept idle in the pool waiting for the user to come.

It is recommended that both of those parameters should be set to equal value and that value should be expected number of HTTP requests serviced concurrently. Otherwise, you will note high Application Module creation/removal. This will also impact performance negatively. Please note that both those parameters refer to referenced and unreferenced AMs that have been released

Just to demonstrate how those parameters influence the pool consider the following situation: Assume that you have a pool which has been configured for normal workload of 50 concurrent users. Let’s say that the number of requests serviced concurrently at average is 10. So assume that jbo.ampool.minavailablesize and jbo.ampool.maxavailablesize are set to 10. So in a normal situation we will have 50 AMs in the pool that are frequently used and the pool will make sure that at any given moment there are at least 10 AMs available in the pool. Assume that there is a peak time and we got 100 concurrent users working on the application. So the pool will have to create extra 50 AMs to accommodate this situation. But once the pick time is over we get back to our regular workload. But we have 100 AMs in the pool of which 50 don’t get used, but some if not all of those inactive 50 AMs are referenced (i.e. have the state of the Data Control request from which they serviced last). This is true only if this AM has been released with stateful release level. When the time specified in jbo.ampool.maxinactiveage will elapse for AM since it last being used it will be marked for removal from AM pool (because at the moment we have more inactive AMs then specified in jbo.ampool.maxavailablesize) and so the pool will be “shrinking,” removing extra AM instances, until it leaves only 10 inactive AMs in the pool. But if failover is not enabled each such AM holds the state that has not been passivated (again if it was released with stateful level). So in this case (failover is not enabled) before AM is removed from the pool it will get passivated in order to preserve its state and keep it accessible.

Further discussion of ADF-BC tuning is out of scope of this document. Please refer to How to Performance Tune an ADF Business Components Application.

Testing ADF Business Components

A very interesting test tool could be made by overriding just few methods in class that extends oracle.jbo.server.ApplicationModuleImpl. Add two variables to your class:

static int idProducer = 0;
private int amId;

amId will be variable that uniquely identifies AM instance. Override the constructor as follows:

public AppModuleImpl() {
  amId = ++idProducer;
  System.out.println("AM with ID " + amId + " is being created"); 
}

You may add and expose to your clients the following method

public int getAmId() {
  return amId;
}

And add these three methods as well:

protected void afterConnect() {
  System.out.println("Application Module with id " +amId +
                     " just got connected");
  super.afterConnect();
}
protected void prepareSession(oracle.jbo.Session session) {
  System.out.println("Application Module with id " + amId +
                     " is about to be used by different session");
  super.prepareSession(session);
}
protected void finalize() {
  System.out.println("Application Module with id " + amId +
                     " is getting garbage collected");
  super.finalize();
}

Those methods will put out diagnostic printouts that will help developer to understand what happens behind the scenes and how and when different events occur. You will see that if AM pooling is on and Connection pooling is off AM gets connected right after it’s creation and holds to its connection for entire life duration. It will get disconnected only when it will be removed from AM pool.

The fact that prepareSession has occurred means that AM has been recycled, i.e. its state has been reset and it assumed the state of different Data Control then the one it serviced before. Of course this method will be invoked always when AM will service request for the first time in its life.

On your client you may get reference to your AM and use method getAmId to see which AM is servicing Data Control from Binding Context related to your current HTTP session. If your AM pool is configured correctly and recycle threshold is higher then the number of HTTP sessions serviced at the moment you will see that usually your HTTP session will always be serviced by the same AM instance.

To get reference to your AM instance on your client side you may create an action for your JSP page and override the method handleLifecycle as follows

public class DeptEmpBrowserAction extends DataForwardAction {
  protected void handleLifecycle(DataActionContext ctx) throws Exception {
    HttpServletRequest request = actionContext.getHttpServletRequest();
    BindingContext bc = ctx.getBindingContext();
    YourModule am = (YourModule)bc.findDataControl("YourModuleDataControl")
                                  .getDataProvider();
    System.out.println("Message from client: The AM with id "+
                       am.getAmId() + " is in use");
    super.handleLifecycle(actionContext);
  }
}

For the testing purposes you may want to disable AM pooling to see that in that case every single request is serviced by different AM instance. And if you create some state that is carried over between requests you will know for sure that it is not because your Data Control received the same AM instance but because your state has been successfully passivated by previous AM and activated by a different one.

Another testcase is to enable AM pooling and set recycle threshold to 1 and open two different HTTP sessions (two different browsers such as Internet Explorer and Mozilla or two different windows of Internet Explorer that are executed in different processes.) You will see that with this parameter you will be juggling AM instances between your sessions.

There are many other cases that such simple tool could be used to demonstrate on practice the complex features described in this paper.

false ,,,,,,,,,,,,,,,