28.4 What State Is Saved and When is It Cleaned Up? 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. 28.4.1 What State is Saved? The information saved as part of the application module passivation "snapshot" includes the following. Transactional State * New, modified, and deleted entities in the entity caches of the root application module for this user session's (including old/new values for modified ones). Non-Transactional State * For each active view object (both statically and dynamically created): o Current row indicator for each row set (typically one) o New rows and their positions (New rows are treated differently then updated ones. Their index in the VO is traced as well) o ViewCriteria and all its related parameters such as view criteria row etc. o Flag indicating whether or not a row set has been executed o Range start and Range size o Access mode o Fetch mode and fetch size o Any view object-level custom data o 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. 28.4.2 Where is the State Saved? By default, passivation snapshots are saved in the database, but you can configure it to use the file system as an alternative. 28.4.2.1 How Database-Backed Passivation Works The passivated XML snapshot is written to a BLOB column in a table named PS_TXN, using a connection specified by the jbo.server.internal_connection property. Each time a passivation record is saved, it is assigned a unique passivation snapshot ID based on the sequence number taken from the PS_TXN_SEQ sequence. The ADF session cookie held by the application module data control in the ADF binding context remembers the latest passivation snapshot ID that was created on its behalf and remembers the previous ID that was used. 28.4.2.2 Controlling the Schema Where the State Management Table Resides The ADF runtime recognizes a configuration property named jbo.server.internal_connection that controls which database connection/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 28.4.2.3 Configuring the Type of Passivation Store Passivated information can be stored in several places. You can control it programmatically or by configuring an option in the application module configuration. The choices are database or a file stored on local file system: * File This choice may be the fastest available 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 28.4.3 When is the State Cleaned Up? Under normal circumstances, the ADF state management facility provides automatic cleanup of the passivation snapshot records. 28.4.3.1 Previous Snapshot Removed When Next One Taken When a passivation record is saved to the database on behalf of a session cookie, as described above, this passivation record gets a new, unique snapshot ID. The passivation record with the previous snapshot ID used by that same session cookie is deleted as part of the same transaction. In this way, assuming no server failures, there will only ever be a single passivation snapshot record per active end-user session. 28.4.3.2 Passivation Snapshot Removed on Unmanaged Release The passivation snapshot record related to a session cookie is removed when the application module is checked into the pool with the unmanaged state level. This can occur when: * Your code specifically calls resetState() on the application module data control. * Your code explicitly invalidates the HttpSession, for example, as part of implementing an explicit "Logout" functionality. * The HttpSession times out due to exceeding the session timeout threshold for idle time and failover mode is disabled (which is the default). In each of these cases, the application module pool also resets the application module referenced by the session cookie to be "unreferenced" again. Since no changes were ever saved into the underlying database tables, once the pending session state snapshots are removed, there remains no trace of the unfinished work the user session had completed up to that point. 28.4.3.3 Passivation Snapshot Retained in Failover Mode When the failover mode is enabled, if the HttpSession times out due to session inactivity, then the passivation snapshot is retained so that the end user can 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 the HttpSession times out, the user will have to go through authentication process, and all unsaved changes are lost. 28.4.4 Approaches for Timing Out the HttpSession Since HTTP is a stateless protocol, the server receives no implicit notice that a client has closed his browser or gone away for the weekend. Therefore any 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. 28.4.4.1 Configuring the Implicit Timeout Due to User Inactivity You configure the session timeout threshold using the session-timeout tag in the web.xml file. The default value is 35 minutes. When the HttpSession times out the BindingContext goes out of scope, and along with it, any data controls that might have referenced application modules released to the pool in the managed state level. The application module pool resets any of these referenced application modules and marks the instances unreferenced again. 28.4.4.2 Coding an Explicit HttpSession Timeout To end a user's session before the session timeout expires, you can call the invalidate() method on the HttpSession object from a backing bean in response to the user's click on a "Logout" button or link. This cleans up the HttpSession in the same way as if the session time had expired. Using JSF and ADF, after invalidating the session, you must perform a redirect to the next page you want to display, rather than just doing a forward. Example 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. 28.4.5 Cleaning Up Temporary Storage Tables 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; . / 28.5 Managing Custom User Specific Information It is fairly common practice to add custom user-defined information in the application module in the form of member variables or some custom information stored in oracle.jbo.Session user data hashtable. The ADF state management facility provides a mechanism to save this custom information to the passivation snapshot as well. By overriding the 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: 1. Retrieve the value of the value to save. 2. Create an XML element to contain the value. 3. Create an XML text node to represent the value. 4. Append the text node as a child of the element. 5. Append the element to the parent element passed in. Note: The API's used to manipulate nodes in an XML document are provided by the Document Object Model (DOM) interfaces in the org.w3c.dom package. These are part of the Java API for XML Processing (JAXP). See the JavaDoc for the Node, Element, Text, Document, and NodeList interfaces in this package for more details. The overridden activateState() method performs the reverse job by doing the following: 1. Search the element for any jbo.counter elements. 2. If any are found, loop over the nodes found in the node list. 3. Get first child node of the jbo.counter element. It should be a DOM Text node whose value is the string you saved when your passivateState() method above got called, representing the value of the jbo.counter attribute. 4. Set the counter value to the activated value from the snapshot. 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 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 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 the ViewObjectImpl class and the EntityObjectImpl class to save custom state for those objects to the passivation snapshot as well. 28.6 Managing State for Transient View Objects 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 28.7 Using State Management for Middle-Tier Savepoints In the database server you are likely familiar with the savepoint feature that allows a developer to rollback to a certain point within a transaction instead of rolling back the entire transaction. An application module offers the same feature but implemented in the middle tier. 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. 28.8 Testing to Ensure Your Application Module is Activation-Safe If you have not explicitly tested that your application module functions when its pending state gets activated from a passivation snapshot, then you may encounter an unpleasant surprise in your production environment when heavy system load "tests" this aspect of your system for the first time. 28.8.1 Understanding the jbo.ampool.doampooling Configuration Parameter The jbo.ampool.doampooling configuration property corresponds to the Enable Application Module Pooling option in the Pooling and Scalability tab of the 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. 28.8.2 Disabling Application Module Pooling to Test Activation 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. 28.9 Caveats Regarding Pending Database State 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. 28.9.1 Web Applications Should Use Optimistic Locking Oracle recommends using optimistic locking for web applications. Pessimistic locking, which is the default, should not be used for web applications as it creates pending transactional state in the database in the form of row-level locks. If pessimistic locking is set, state management will work, but the locking mode will not perform as expected. Behind the scenes, every time an application module is recycled, a rollback is issued in the JDBC connection. This 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. 28.9.2 Use PostChanges Only During the Current Request 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. 28.9.3 Pending Database State Across Requests Requires Reserved Level 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. 28.9.4 Connection Pooling Prevents Pending Database State 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.