Friday Apr 20, 2012

ADF Prematurely Terminated Task Flows

In this post I'll describe some interesting side effects on task flow transactions if a task flow terminates/finalizes earlier than expected.  To demonstrate this we'll use the following application built in JDev

The app based on the Oracle HR schema renders a single page:

Before describing the prematurely terminating task flow behavior let's describe the characteristics of the application first:

1) The app makes use of 2 independent view objects DepartmentsView and EmployeesView.

2) The overall page is Main.jspx which has an embedded region calling the departments-task-flow which itself has another embedded region calling the employees-task-flow.  The departments task flow has the editable departments form and navigation buttons to walk the departments, the employees task flow the table of relating employees for the department.

3) As the user navigates between departments records, the department ID is passed to the employees task flow which calls an ExecuteWithParams activity then displays the resulting employees.  The employees task flow binding has it's refresh = ifNeeded and the associated region has partialTriggers on the navigation buttons, ensuring the employees task flow is updated as the user navigates the departments using the supplied buttons.

4) Of particular interest, the departments-task-flow is using the Always Begin New Transaction task flow transaction behaviour and has an isolated data control scope:

And the employees-task-flow is using Use Existing Transaction if Possible and a shared data control scope:

If you run this application and navigate amongst the departments using the navigation buttons, the application works as expected.  Both the departments and employees records move onto the next departments ID after each button click.

In the application I've also added some ADFLoggers which help capture the current behaviour:

<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/departments-task-flow.xml#departments-task-flow initialized
<AppModuleImpl> <create> AppModuleImpl created as ROOT AM
<AppModuleImpl> <prepareSession> AppModuleImpl prepareSession() called as ROOT AM
<AppModuleImpl> <create> AppModuleImpl created as NESTED AM under AppModule
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized
<TaskFlowBean> <taskFlowFinalizer> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow finalized
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized

As the page first renders we can see the departments task flow initialized then the associated application module created and prepared to serve the data for the DepartmentsView.  Subsequently we can see the employees task flow initialized.  We don't see a new application module as the employees task flow is sharing the data control. From here each time we step onto another departments record, we'll see the employees task flow finalizer called, then the employees task flow intializer called.  This occurs because the ifNeeded refresh property on the employees task flow is restarting the task flow each time the department ID is changed.

This restarting of the task flow is what I coin the "premature termination" of the task flow.  Essentially the calling parent has forced the framework to terminate the employees task flow, rather than the task flow gracefully exiting via a task flow return activity.

At the moment though, this is still a "So what?" scenario.  What do we care?  Everything appears to work?

Let's change the setup slightly to demonstrate something unexpected.  Return to the application and set the departments task flow transaction option to <No Controller Transaction> (and leave the data control scope option = isolated/unselected):

Rerun the application.  Note now when it runs and we press one of the navigation buttons, besides a screen refresh nothing happens.  We don't walk onto a new departments record in the departments task flow, and we don't see the associated employees for the expected new department.  The application seems stuck on the first department.

A clue to what's going on occurs in the logs:

<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/departments-task-flow.xml#departments-task-flow initialized
<AppModuleImpl> <create> AppModuleImpl created as ROOT AM
<AppModuleImpl> <prepareSession> AppModuleImpl prepareSession() called as ROOT AM
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized
<TaskFlowBean> <taskFlowFinalizer> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow finalized
<AppModuleImpl> <beforeRollback> AppModuleImpl beforeRollback() called as ROOT AM
<TaskFlowBean> <taskFlowInit> Task flow /WEB-INF/employees-task-flow.xml#employees-task-flow initialized

Notice in between the last employees task flow finalizer/initializer pair we see the application module has performed a rollback.  This partially explains the behaviour we're seeing.  When a rollback is issued, a rollback resets the current row indicators for all view objects attached to the application module.  This is why we can't move onto another record.

But why is the rollback called in this scenario?

The answer is wrapped around the concept of task flow transactions and the associated data control frame.

In the first scenario the departments task flow initiated the task flow transaction and associated data control frame.  In turn the employees task flow joined the departments transaction and data control frame.  Only the initiator of a task flow transaction can commit/rollback the overall transaction associated with the data control frame.  In the case where the employees task flow is prematurely terminated, as it a secondary citizen in the overall task flow transaction, the framework leaves the initiator of the task flow to tidy up the transaction. No automatic rollback occurs on the work done by the secondary task flow.

In the second scenario the departments task flow is not initiating the task flow transaction as it's chosen the <No Controller Transaction> option.  Instead the employees task flow initiates the transaction because when the Use Existing Transaction if Possible option finds no transaction open it defaults to the equivalent of Always Begin New Transaction behaviour.

In remembering the initiator of a task flow transaction can commit/rollback the overall transaction, the framework automatically rolls back the employees task flow and this is the cause of the behaviour we're seeing.  Even though the departments task flow is using <No Controller Transaction> this doesn't mean the underlying view object doesn't participate in a transaction, it just doesn't participate in a task flow transaction (which is an abstraction sitting about the data control transactions).  As the two task flows share data controls, there is only a single application module shared by both task flows, a rollback from one task flow will result in a roll back in both task flows.

The solution? Either revert back to the original settings where the bookings task flow uses Always Begin New Transaction, or alternatively use an isolated data control scope for the employees task flow.

Saturday Apr 07, 2012

Oracle JDeveloper 11gR2 Cookbook book review

I recently received a free copy of Oracle JDeveloper 11gR2 Cookbook published by Packt Publishing for review.

Readers of technical cookbooks would know this genre of text includes problems that developers will hit and the prescribed solutions, in this case for Oracle's Application Development Framework (ADF).  Books like this excel themselves on excellent coverage, a logical progress of solutions through out the book, and providing a readable narrative around the numerous steps and code.

This book progresses well through ADF application assembly, ADF Business Components, the view layer, security, deployment and tuning.  Each recipe had a clear introduction and I especially enjoyed the "There's more" follow up sections for some recipes that leads the reader onto related ideas and issues the reader really needs to be aware of.

Also worthy of comment having worked with ADF for over 5 years, there certainly was recipes and solutions I hadn't encountered before, this book gets bonus points for that.

As a reviewer what negatives can I give this text? The book has cast it's net too wide by trying to cover "everything from design and construction, to deployment, testing, debugging and optimization."  ADF is such a large and sophistication technology, this book with 100 recipes barely scrapes the surface.  Don't expect all your ADF problems to be solved here.

In turn there is inconsistency in the level of problems and solutions.  I felt at the beginning the book was pitching itself at advanced problems to solve (that's great for me), but then it introduces topics like building a static View Object or train.  These topics in my opinion are fairly simple and are covered by the Oracle documentation just as well, they shouldn't have been included here. 

In conclusion, ADF beginners will find this book worthwhile as it will open your eyes to the wider problems and solutions required for ADF, and experts for just the fact they can point junior programmers at the book for certain problems and say "get on with it".

Is there scope for more ADF tombs like this?  Yes!  I'd love to see a cookbook specializing on ADF Business Components (hint hint to budding authors).

Tuesday Apr 03, 2012

Solution for developers wanting to run a standalone WLS 10.3.6 server against JDev

In my previous post I discussed how to install the ADF Runtimes into a standalone WLS 10.3.6 server by using the ADF Runtime installer, not the JDeveloper installer.  Yet there's still a problem for developers here because JDeveloper comes coupled with a WLS 10.3.5 server.  What if you want to develop, deploy and test with a 10.3.6 server?  Have we lost the ability to integrate the IDE and the WLS server where we can run and stop the server, deploy our apps automatically the server and more?

JDeveloper actually solved this issue sometime back but not many people will have recognized the feature for what it does as it wasn't needed until now.

Via the Application Server Navigator you can create 2 types of connections, one to a remote "standalone WLS" and another to an "integrated WLS".  It's this second option that is useful because what we can do is install a local standalone WLS 10.3.6 server on our developer PC, then create a separate "integrated WLS" connection to the standalone server.  Then by accessing your Application's properties through the Application menu -> Application Properties -> Run -> Bind to Integration Application Server option we can choose the newly created WLS server connection to work with our application.

In this way JDeveloper will now treat the new server as if it was the integrated WLS.  It will start when we run and deploy our applications, terminate it at request and so on.  Of course don't forget you still need to install the ADF Runtimes for the server to be able to work with ADF applications.

Note there is bug 13917844 lurking in the Application Server Navigator for at least JDev and earlier.  If you right click the new connection and select "Start Server Instance" it will often start one of the other existing connections instead (typically the original IntegratedWebLogicServer connection).  If you want to manually start the server you can bypass this by using the Run menu -> Start Server Instance option which works correctly.


Not a selfie
Chris Muir
Oracle Mobility and Development Tools Product Manager

The views expressed on this blog are my own and do not necessarily reflect the views of Oracle.



« April 2012 »