Sunday, October 30, 2011

This post provides sample code to cover complete use case scenario for mobile and simplify initial development phase for ADF mobile browser application. As it was presented on OOW this year, in the future we will have advanced ADF mobile support for HTML5, mobile application will leverage advanced functionalities offered by mobile frameworks. This is future, I will describe what is available today. Read more about ADF Mobile - Mobile Application Development with Oracle ADF Mobile.

Download sample application - MobileApp.zip. This sample was created by aggregating different resources from Oracle ADF Mobile developer guide. It is designed with JDev 11g R2, but it works with JDev 11g R1 as well (just few minor changes will be needed for CSS style manager class). Additionally to information from developer guide related to mobile functionality, I went one step further and completed ADF part as well. I believe this may help to design and implement ADF mobile browser applications easier. You can use provided sample to Jump Start your mobile development with Oracle ADF.

Sample application was designed and optimized for Apple iPhone, however it can run on other platforms as well. Implemented functionality:

ADF Security with Login/Logout support

Search screen with search results displayed in a table

Transition from search results screen to details screen

Editable details screen

Save/Undo functionality for editable data

In order to develop web applications optimized for iPhone platform, you should own Apple MacBook. If you have one, download Apple Xcode for free (development IDE from Apple) and run iPhone/iPad simulator application. Run iPhone simulator from Developer/Platforms/iPhoneSimulator.platform/Developer/Applications/ folder. You don't need to have real iPhone, just test with simulator:

If you run provided sample application from JDeveloper 11g R2, or deploy to standalone WebLogic - access application through URL, directly from iPhone Safari browser:

Type username and password, authenticate into system by pressing Login button:

Immediately after login, user is redirected to browse/search page. Search criteria is implemented as choice list - there are no results for default selection:

Click on choice list and select another search criteria:

List with results will be displayed, user will be able to click on results record:

Open editable details screen with different actions:

We saw application screens, now let's look into application source code. It is developed as ADF MVC application, with one difference - we cant use ADF regions, because ADF Mobile Web is implemented with Trinidad and Trinidad is not supporting ADF region tag. Model is based on ADF BC, as any regular ADF application. There is special CSS file as well, this file contains CSS tailored for mobile browsers and special mobile platform looks (iPhone skin for example):

Create JSPX pages, with mobile support, so all Trinidad tag libraries will be included. Make sure to define viewport as well, viewport is important for proper UI stretching on mobile device screen:

You should use predefined style classes for different Trinidad UI components, this will allow to achieve native mobile browser look and feel (these style classes are available from custom CSS file included with sample application):

There is some Java logic implemented as well. When user is changing search criteria choice list value, we are invoking Execute action - this will trigger VO refresh based on new bind variable value. For proper transition from browse screen to the editable details screen, we need to update current row key (otherwise details screen always will show first record available in VO rowset):

When data changes are reverted from editable screen, we should stay on current record (by default focus would jump to the first record from VO rowset). This can be implemented by performing row refresh:

You may wonder, how we are applying search criteria. Criteria is declared to be applied directly for VO instance, in Application Module - every time when VO will be refreshed or accessed - predefined View Criteria will be applied automatically:

There is no need to set bind variable value directly, because bind variable is predefined with Groovy expression language and retrieves value from VO implementation class method:

This method is accessing choice list VO and gets current row, its how we are able to get retrieve search criteria parameter value:

Login action is based on standard ADF Security method:

For successful redirect to Login page, make sure anonymous access is granted:

Saturday, October 29, 2011

This week we were performing advanced ADF BC Application Module pool tuning. There are few lessons learned, I would like to share with you. Tests were performed using latest JDeveloper/ADF release 11g R2 (11.1.2.1.0) (Build 6081). Test case is based on sample application from Optimizing Oracle ADF Application Pool post. This sample contains two Application Modules, one is with default AM tuning settings, second AM is tuned:

2 minutes Time to Live

1 minute Idle Instance Timeout

0.5 minute Pool Pooling Interval

Experiment:

Deploy sample application with AM tuning into ADF Library

Import ADF Library into Main Application

Main Application contains its own local AM

Verify if tuning for AM from ADF Library was applied successfully

Lessons learned:

There is no guarantee that AM tuning options will be applied always

Applied AM tuning options might depend on first AM initialized

AM Reserved mode might be more stable, comparing to AM Managed mode

AM passivation/activation relationship with database transaction:

AM passivation happens - database transaction is lost for current user (database lock is lost)

Make sure no AM passivation happens during transaction period

By default, AM is running in Managed mode, this means AM is running with AM pool enabled. If we need to support critical transactional operations, we need to ensure AM Passivation will not happen during predefined period of time. However, it looks like there is no guarantee, even after applying proper tuning parameters - AM running in Managed mode, can be still passivated (even there is enough space in AM pool). This is a main reason, why you may prefer to run AM in Reserved mode, to ensure long running transactions. Read more about Managed and Reserved modes - 43.2.2.3 About State Management Release Levels. Please test all tuning options carefully, before applying.

Test case application implements two AM's and two ADF TF's for each AM:

This application is deployed into ADF library and imported into Main application. As described above, one AM is tuned to be destroyed in 2 minutes:

Main application implements two physical JSPX pages. First page brings data from local AM, second page integrates ADF regions from imported library:

As you can see from DataBindings file for Main application, there is local Data Control usage declared:

If we check the same for application packaged into ADF library, this application contains two Data Control usages - both pointing to different AM's (one AM is tuned):

Test was performed with Shared Data Control and with Isolated Data Control - there was no difference in results. Default - Shared Data Control for ADF task flow:

How to reproduce performed test:

1. Run Main application and access first page - local Data Control is loaded:

2. Press Edit button - navigate to second page. Data Control from imported library will be loaded:

3. Imported library contains tuning for AM to be destroyed after 2 minutes of inactivity. This tuning option is ignored, AM instance still keeps database connection active even after 2 minutes of inactivity:

4. From Main application, expand Data Controls section. Expand HrModuleDataControl imported from ADF library, drag and drop EmployeesView1 into first page of Main application:

5. This time, AM from imported library gets initialized much earlier, when first page from Main application is loaded. We have different behavior now - AM tuning options are applied and AM instance is destroyed, database connection is released after 2 minutes of inactivity:

Main goal of this post was to describe inconsistent behavior of AM pool tuning. If you will experience similar issues with AM tuning, please report through Oracle Metalink - this will help to improve AM pooling.

Sunday, October 23, 2011

Back to the basics. In most of the cases data is coming from Model layer, from ADF BC or EJB. When user is changing data on the screen, frameworks takes care and preserves temporary data. However, what about such screens where we have temporary fields, without any relation with the Model layer - transient data fields. What if there is no corresponding Model implementation, and still we need to store field data between requests - where should we store it? I believer, one of the best techniques is to use Page Definition variables, this is old approach back from ADF 10g times - but it still works very well. Main advantage - we are able to store transient temporary data between requests and there is no need to define session scope bean.

It happened to see scary things - developers are defining managed beans in Page Flow Scope, just because they want to make sure temporary UI values are preserved between requests :)

Download sample application - PageDefVariables.zip. This sample implements two input components - inputText and inputNumberSlider. Both components are enabled with autoSubmit, as well as value change listener is defined for both. Once value is changed for one of the components, we recalculate total sum of both fields and display as total:

Both input components exist only in UI, there is no Model layer representation - user types values, total is recalculated immediately. However, we still need to store entered values, otherwise once we select number slider - partial request will be invoked and value entered for income will be lost. ADF developer would try to store input value inside Backing Bean:

It would be wrong to store temporary value inside Backing Bean - this bean lifetime is as long as request, in other words - once we will enter second input value, first will be always lost. You may think to define Session or even Page Flow Scope bean - that would make it work, but why to waste server memory - only if you want to make your ADF applications slow.

We can use Page Definition variables to store temporary values, these values will be preserved between requests. Open Page Definition, associated with the page, expand executables and select to insert new variable:

Give a name and specify a type for new variable:

In my example, I will define all three variables in the same way - for each of the fields:

Once variables are defined, we are not done yet with Page Definition. For every defined variable we need to define corresponding attribute (will be accessed from the page). Insert new item under bindings - Attribute Value:

When creating new Attribute Value, make sure to select variables as Data Source and map related variable (defined one step above) name:

Once all done, we should have following picture - each variable is assigned with attribute value:

Go back to UI now, input component should have its value property mapped with attribute value (not with variable directly, because data will not be stored/retrieved) from Page Definition. For example: bindings.incomeVarAttr.inputValue:

Once again - UI component value you should map with attribute value, not with variable directly:

Finally, you may ask question - how to access attribute values defined in Page Definition programmatically? Easy - use ADFUtils wrapper class. There are two methods available for your convenience - getBoundAttributeValue("name") and setBoundAttributeValue("name", value):

There are use cases, when we want to control command component action execution, based on logic from action listener. If some conditions implemented by action listener are met, we would like to prevent action execution. For today post, I will take example of WebCenter 11g PS3/PS4 navigation and explain how to prevent pprnav action, when there are unsaved changes in the system.

In order to be able to discover unsaved changes, instead of pointing to WebCenter navigation context, action listener should point to our custom bean - navigationBean. There is custom processAction(...) method defined inside navigationBean:

This method checks current data control frame for any unsaved changes, based on this check navigation is performed or unsaved changes message is shown.

Sample application contains one task flow, where two fragments are defined. First fragment brings list of jobs, and second displays selected job and allows to edit job details:

We will see now, what will happen when there are unsaved changes in the second fragment and user clicks on WebCenter menu. We select job for editing:

Open selected job details, edit data:

Without saving or reverting changes, click on any item from WebCenter menu. Our custom processAction(...) method will be invoked, where current data frame will be checked for unsaved data - unsaved changes message will be shown to the user and further navigation will be prevented. However - we loose editing screen and are navigated back to the first fragment from the task flow:

What is the reason for this? Logically - because WebCenter menu defines pprnav action by default, even we prevent further navigation from custom action listener, action method is still invoked:

There is a way to control action from action listener. If there are unsaved changes, we can get instance of RichCommandLink menu item and set action expression to be Null. Keep in mind, action method is invoked always after action listener method - this would mean that in our case, because we are setting action expression to be Null - action will not be invoked at all, when there are unsaved changes and we will stay on current fragment:

Once action expression is set to be Null, it will remain as Null always for life span of current session. This means for next request we need to reset it to be pprnav again. We can set action expression to be pprnav, if there is unsaved data - it will be tracked during same request and action expression will be set back to Null again: