Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g Release 3 (10.1.3.0) Part Number B25947-02 |
|
|
View PDF |
This chapter describes the application module state management facility and how to use it.
This chapter includes the following sections:
Section 28.1, "Understanding Why State Management is Necessary"
Section 28.2, "The ADF Business Components State Management Facility"
Section 28.3, "Controlling the State Management Release Level"
Section 28.4, "What State Is Saved and When is It Cleaned Up?"
Section 28.7, "Using State Management for Middle-Tier Savepoints"
Section 28.8, "Testing to Ensure Your Application Module is Activation-Safe"
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.
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.
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 28-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.
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.
As shown in Figure 28-2, the technique 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 simplicity's sake, 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, along with a way of identifying what site sent the cookie and 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 the requests made by different users. A cookie that expires when you close your browser is known as a "session cookie," while other cookies set to live beyond a single browser session might expire in a week, a month, or a year from when they were first created.
J2EE-compliant web servers provide a standard server-side facility called the HttpSession
that allows a web developer 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 developer while handling a subsequent request during the same session. The session stays "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.
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, since the session-scope Java objects you create are held in the memory of the J2EE web server, the objects in the HTTP session are lost if the server should fail.
As shown in Figure 28-3, one way to improve the reliability is to configure multiple J2EE servers in a cluster. By doing this, the J2EE 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.
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.
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 involves carefully deleting all of the eagerly-committed rows in possible multiple tables.
These limitations have lead 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.
The application module component and application module pool cooperate to offer a generic solution to database-backed, application state management. This feature 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 the previous section.
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 used 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 28-4 illustrates the basic architecture of the state management facility to support these multi-step scenarios. An application module supports passivating 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.
The ADF binding context is the one object that lives in the HttpSession
for each end user. It hold references to lightweight application module data control objects that manage acquiring an application module instance from the pool at the beginning of each request 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, if you have multiple application servers and you enable the optional ADF Business Components failover support, then subsequent end-user requests can be handled by any server in your server farm or cluster. The ADF session cookie saved in the client browser is enough to "reactivate" the pending application state from the database-backed XML snapshot if required, regardless of what server handles the request.
To better understand when the automatic passivation and activation of application module state occurs, consider the following simple case:
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.
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.
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.
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:
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
.
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.
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.
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.
There is a parameter called jbo.dofailover
that can be set in your application module configuration on the Pooling and Scalability tab of the Configuration Editor. This parameter controls when and how often passivation occurs. 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.
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 embedded OC4J server. The ADF failover mechanism has no way of knowing whether you stopped the embedded 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 doing the latter, Oracle recommends exiting out of your browser before restarting the application on the embedded 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.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.
ADF supports the release levels described in the following sections.
This is the default level. This level 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 thejbo.ampool.doampooling
configuration property is false
— corresponding to your unchecking the Enable Application Module Pooling option in the Pooling and Scalability tab of the Configuration Editor — 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, you'll typically never run a production system with this option set to false
.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 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 (a classic example is releasing the application module after servicing the HTTP request from logout page).
This mode was called the "Stateless Release Mode" in previous releases of JDeveloper.
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. In general, it is strongly recommended never to use this mode. 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.
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.
Note:
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.If you do not want to use the default "Managed State" release level, you can set your desired level programmatically. Use the API's describe in the following section:
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 theHTTPSession
times out, as described below.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.
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
.
The sections below illustrate four different contexts you might make use of these release mode API's.
Example 28-1 shows calling the resetState()
method on a data control named UserModuleDataControl
from the action method of a JSF backing bean.
Example 28-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); } }
Example 28-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 28-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); } }
Example 28-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 customPagePhaseListener
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 28-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); } }
If you wanted to build an ADF application where every request was handled in a completely stateless way, use a global custom PageLifecycle
class as shown in Example 28-4. See Section 10.5.4.1, "Globally Customizing the ADF Page Lifecycle" for details on how to configure your application to use your custom lifecycle in a global way.
Example 28-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); } }
The information saved by 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 ORDERBY clause.
The information saved as part of the application module passivation "snapshot" includes the following.
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).
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 VO is traced as well)
ViewCriteria and all its 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
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. See Section 5.5.3.2, "Enabling ADF Business Components Debug Diagnostics" for more information on how to enable diagnostics.By default, passivation snapshots are saved in the database, but you can configure it to use the file system as an alternative.
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.
The ADF runtime recognizes a configuration property named jbo.server.internal_connection
that controls which database connection/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 will use a different connection instance from the database connection pool, but the database credentials will be the same as the current user. Since the framework creates temporary tables, and possibly a sequence if they don't already exists, 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:
someuser
/
somepassword
@
host
:
port
:
SID
A JDBC datasource name like:
java:/comp/env/jdbc/YourJ2EEDataSourceName
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 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) is either installed on the same machine or have 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 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
Under normal circumstances, the ADF state management facility provides automatic cleanup of the passivation snapshot records.
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.
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.
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 continue their work when they return to their 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. In failover mode, the ADF runtime saves an additional browser cookie that the ADF runtime uses to track the latest passivation snapshot ID for each client running an application in failover mode. 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. The additional browser cookie is used to reactivate any available application module instance with the user's last pending state snapshot before handling the request.
Note:
If an application module was released with reserved level then theHttpSession
times out, the user will have to go through authentication process, and all unsaved changes are lost.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 J2EE-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.
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.
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 28-5 shows the code used by the SRLogout.java
backing bean in the SRDemo application to perform this task.
Example 28-5 Programatically termin
// In SRLogout.java, backing bean for the logout.jspx page 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("SRWelcome.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.
JDeveloper supplies the bc4jcleanup.sql
script in the ./BC4J/bin
directory to help with periodically cleaning up the state management table. 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 28-6 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 28-6 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 BC4J Cleanup 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; . /
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 following two methods on the ApplicationModuleImpl
class, you can write out and read back your custom information:
protected void passivateState(Document doc, Element parent) public void activateState(Element elem)
Consider the following simple example that shows how to override this pair of methods to ensure custom application module state is included in the passivation/activation cycle. Assume that you would like to keep some custom parameter in your application module called jbo.counter
whose value 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 28-7 shows the code you can write in your pair of overridden passivateState()
and activateState()
methods in a custom application module class to do the job.
The overridden passivateState()
method performs the following steps:
Retrieve the value of the value to save.
Create an XML element to contain the value.
Create an XML text node to represent the value.
Append the text node as a child of the element.
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 theorg.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.The overridden activateState()
method performs the reverse job by doing the following:
Search the element for any jbo.counter elements.
If any are found, loop over the nodes found in the node list.
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.
Set the counter value to the activated value from the snapshot.
Example 28-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";
Note:
Similar methods are available on theViewObjectImpl
class and the EntityObjectImpl
class to save custom state for those objects to the passivation snapshot as well.Each view object can be declaratively configured to be passivation-enabled or not by using the Passivate State checkbox on the Tuning page of the View Object Editor. If a view object is not passivation enabled, then no information about it gets written in the application module passivation snapshot.
For passivation/activation purposes, both transient and SQL-calculated view object attributes are treated in the same way.
Transient view object attributes are not passivated by default. Due to their nature, they are usually intended to be "read only" and are very easily recreateable. So, it often doesn't make sense to passivate their values as part of the XML snapshot. However, by checking the Passivate checkbox on the Attribute page of the View Object Editor for any transient attribute, you can declaratively configure it to be passivation-enabled.
By default, all view objects are marked as passivation-enabled, and all transient attributes are not. That means that 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.
It is worth noting that 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 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 only include the view object attributes marked for passivation).
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. 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)
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 (i.e. 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.
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.
The jbo.ampool.doampooling
configuration property corresponds to the Enable Application Module Pooling option in the Pooling and Scalability tab of the Configuration Editor. By default, this checkbox is checked so that application module pooling is enabled. Nearly always this default setting of jbo.ampool.doampooling
to true
is the way you will run your applications in production. However, setting the property to false
should 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.
Oracle recommends, as part of your overall testing plan, that you 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.
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 embedded OC4J 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 28.5, "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.
As you have seen, 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.
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 would release all the locks that pessimistic locking had created.
To change your configuration to use optimistic locking, open the Properties tab of the Configuration Editor and set the value of the jbo.locking.mode
to optimistic.
As you saw in Section 9.2.2, "Understanding Commit Processing and Validation", using the postChanges()
method needs to be done with careful consideration, if at all. Nowhere is this more true than when considering state management, application pooling, and database connection pooling. The postChanges()
method should only be used as part of the commit processing lifecycle so that any pending database state it creates is committed or rolled back during the same request.
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. Oracle recommends that this be as short a period of time as possible between creation of transactional state in the database performing the concluding commit or rollback, so that reserved level doesn't have to be used for long time. This is due to the fact that the reserved level 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.
When you check the Disconnect Application Module Upon Release property on the Pooling and Scalability tab of the Configuration Editor, this translates to setting the jbo.doconnectionpooling
configuration parameter to true
.
With this connection pooling option enabled — 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 no longer there.