Friday Jul 20, 2012

Facelets and PanelDashboard Gotchya

An issue that came to light via the ADF Expert Methodology Group last week seemed to be an interesting one to write about. Olga had logged an issue in the EMG Issue Tracker which seemed to imply that there was a difference in behaviour between JSP and Facelets when  using the <af:panelDashboard> component.  Specifically, the problem seemed to be that when you made changes to the ordering of the panels within the dashboard, under JSP the order would be remembered, but the same page and code under Facelets (ADF 11.1.2.n)  the change would be ignored.

This naturally piqued my interest and the detective work began. Running the test-case that Olga had thankfully supplied and then that our own Chris Muir had further simplified,  the problem certainly reproduced, so it seemed to be real, however, reading the code there was a clue to what was going on.  Olga had sensibly adapted the panelDashboard sample from the ADF Demo WAR file and the code in question that was called when the relevant operation took place contained an interesting comment. Here it is:

// Apply the change to the component tree immediately:
// Note that the ChangeManager.addComponentChange() is required here if using Facelets because the Facelets view
// handler will discard the change in order when the next render phase is started.
ComponentChange change = new ReorderChildrenComponentChange(reorderedIdList);
RequestContext rc = RequestContext.getCurrentInstance();
rc.getChangeManager().addComponentChange(FacesContext.getCurrentInstance(), dashboard, change);

So there was an admission here that something slightly different was going on in the world of Facelets.  Furthermore, reading the tagdoc on panelDashboard gives a bit more information and a further clue:

You may wish to use org.apache.myfaces.trinidad.change.ReorderChildrenComponentChange and ChangeManager().addComponentChange() if you wish to preserve a new ordering of the children. Ideally, you should use an MDS ChangeManager to persist the change longer than the user's session so that the use does not have to repeatedly make the same reorder gesture every time your application is used

ChangeManager is the magic word here. Back to the testcase, and this time I ran it in debug with the specific aim of inspecting the change Manager instance returned by the  rc.getChangeManager() call above. Sure enough, there was the problem. In the testcase, the ChangeManager was an instance of org.apach.myfaces.trinidad.change.NullChangeManager. If you have a look at the source code for that you'll see that all the operations are No-Ops, so it's not surprising that the change was not persisted. 

So the solution was then obvious, in the project properties, in the ADF View panel, check the Enable User Customizations box to configure a Change Manager (either Session or MDS) that actually does something and the problem is resolved. 

The morals of this story are to read the doc and do not be afraid of debugging code. It will really help, of course, if you make sure you have the ADF Source Code installed (just ask Oracle Support). You can save a lot of time, frustration and needless SRs.

Wednesday Jan 04, 2012

Maintaining Row Currency Separation in Task Flow Instances

So here's a quick thought experiment, how would one create a re-usable task flow which should tick the following boxes:

  1. Multiple copies of the flow are displayed on a single page
  2. Each instance of the taskflow should maintain it's own row currency and not be affected by navigation in any other instance
  3. All instances of the taskflow should be part of the same transactional context

Let's analyse that. Well, Requirement (1) is easy, that's one of the core capabilities of task flows.  Each has it's own pageFlowScope which is kept private so multiple instances on the page should be fine.

Requirement (2) can be achieved simply by setting the data control scope to <isolated/> in the task flow definition. 

Requirement (3) - oh dear.

So there's the problem, it seems that requirements (2) and (3) are mutually exclusive.  If you want everything to use the same transaction you also have to have them sharing the same VO instance and therefore being coordinated from the record currency point of view. 

To solve this let's just think a little outside the box and consider what row currency is and therefore how we could approach the problem.  Each View Object Instance has a primary RowSetIterator  which identifies the "current row" within the VOs collection. As all the instances of the TaskFlow are using the same bindings, they are therefore using the same VO instance and therefore all share the same RowSetIterator (RSI). Now a VO instance can have secondary RSIs but these are really only of use in the programmatic sense, and cannot be wired in through the binding layer.

So logically, one approach to this is to define a second VO instance based on the same VO definition and bind one taskflow to that and the other to the origional. Well that's fine, but we're painted into a corner here, in that we need to decide up-front how many concurrent views we need and then define explicit VO instances in the AM  and separate almost-identical-apart-from-the-bindings taskflows. So this may well work when you just have a couple of views but then it get's unworkable, plus it just feels wrong to duplicate all that taskflow content just to change the VO instance name.

However, in principle, the idea is along the right lines. We just need to simplify it so that we don't have to do any duplication or pre-definition of VO instances. Is it possible? Yes of course, and surprisingly easy.

The Approach 

Getting Started 

The first step here is to create your AM with an initial instance of the VO you need exposed (you can delete it later but having it there will make the design time easier). Once the VO instance is available in the data control palette you can go ahead and create your re-usable taskflows and test them you want to get the basic functionality right after all.  Of course at this stage if you use multiple instances  of the taskflow they will all be coordinated. 

Parameterise the Task Flow  

 At this stage define a task flow parameter which can be passed in to define the unique name for the VO instance that this instance of the taskflow will use. You might write some fancy generator method to create unique names in sequence (DeptView2, DeptView3, DeptView4 and so on) or just hardcode a value when you map the taskflow into a region, it's up to you. Just bear in mind that any taskflow instances that share the same instance name will also share the same RSI and therefore be coordinated - so you can mix and match both coordinated and uncoordinated instances. For the sake of example let's call that parameter pVOInstanceName.

Create a Method to Create  New Instances of the VO

Next we need to define a method that will create a VO instance on demand. This is very simple code, just generate up a Java Impl class for your AM and add a method something like this:

     public void createUniqueVOInstance(String voDefName, String instanceName){
        ViewObject existingVO = findViewObject(instanceName);
        if (existingVO == null) {
  "Creating VO instance for " + instanceName);
            ViewObject newVO = this.createViewObject(instanceName, voDefName);
        else {
  "Reusing VO instance for " + instanceName);

Note how we check to see if that name is already in use first. 

Expose this method as part of the client interface so we can bind that into the taskflow.

Invoke the VO Instance Creation Method from the TaskFlow

Once you've refreshed the data control pallet you should then be able to drag this new AM method (createUniqueVOInstance in my case) into the task flow.  Set that as the default activity on on the flow so that it executes first. You'll pass the full name of the ViewObject definition (e.g. oracle.demo.model.DepartmentsView) into the voName parameter and the instance name you want will come from the parameter you defined for the taskflow (e.g. #{pageFlowScope.pVOInstanceName}). So now when you enter the flow you'll create a new VO instance based on the supplied definition with this name. 

Naturally you then wire this method activity to the rest of the taskflow as normal so that once the instance has been created you can continue to display the page fragments etc.

Rewire the Iterators. 

The final piece of the puzzle is how to tell all of the bindings that you've created within the taskflow to use this newly created VO instance rather than the one that they where created with. This final step is very simple and elegant. for the views and other activities in the taskflow just edit the pageDef XML files and locate all of the iterator bindings that relate to your template VO instance that you used when creating the UI through drag and drop. Now simply change the hard-coded reference to the VO Instance name in the Iterator binding Binds attribute to the expression pointing to your new instance name. e.g. 

    <iterator Binds="#{pageFlowScope.pVOInstanceName}" RangeSize="25"
              DataControl="AppModuleDataControl" id="DepartmentsView1Iterator"

And there you go, nothing else has to change.  Just be sure to make this change throughout all of the pageDefs used by the taskflow otherwise you're in for some funny results.

So there you have it, transactionally linked but independently scrolling instances of the same taskflow. 

And there's more

Once you start to think about it, things get even more interesting here. Because each instance of the taskflow has it's own VO  instance they can also have their own set of view criteria applied and yet all still be visible on the screen at once. 

Thursday May 13, 2010

New ADF Design Paper Covering Task Flows

Just published to OTN today is a new paper that I've put together Task Flow Design Fundamentals. This paper collates a whole bunch of random thoughts about ADF Controller design that I've collected over the last couple of years. Hopefully this will be a useful aid to help you think about your task flow design in a more structured way.

Hawaii, Yes! Duncan has been around Oracle technology way too long but occasionally has interesting things to say. He works in the Development Tools Division at Oracle, but you guessed that right? In his spare time he contributes to the Hudson CI Server Project at Eclipse
Follow DuncanMills on Twitter

Note that comments on this blog are moderated so (1) There may be a delay before it gets published (2) I reserve the right to ignore silly questions and comment spam is not tolerated - it gets deleted so don't even bother, we all have better things to do with our lives.
However, don't be put off, I want to hear what you have to say!


« April 2014