29 Apr 2012

Backing bean scope in ADF task flow finalizer

Introduction
This is very common and recommended practice to use task flow finalizers when we need to do some final job (clean resources, close connections, etc) before the task flow is gone. As usual we work with managed beans declared inside the task flow. The managed beans can have different scopes - request, page flow, view, backing bean, etc. The scope depends on what the bean is actually used for. There is a small problem when we access to the backingBean scope managed bean in the finalizer. Let's have a look at the example bellow.

We have a bounded task flow with page fragments:

And we have manged beans inside the task flow of three different scopes - page flow, view and backingBean:

 <managed-bean id="__3">
  <managed-bean-name id="__5">FlowBean</managed-bean-name>
  <managed-bean-class id="__4">view.BackBean</managed-bean-class>
  <managed-bean-scope id="__2">pageFlow</managed-bean-scope>
 </managed-bean>
 <managed-bean id="__9">
  <managed-bean-name id="__6">ViewBean</managed-bean-name>
  <managed-bean-class id="__7">view.BackBean</managed-bean-class>
  <managed-bean-scope id="__8">view</managed-bean-scope>
 </managed-bean>
 <managed-bean id="__10">
  <managed-bean-name id="__11">BackBean</managed-bean-name>
  <managed-bean-class id="__12">view.BackBean</managed-bean-class>
  <managed-bean-scope id="__13">backingBean</managed-bean-scope>
 </managed-bean>


On the page we have three buttons binded to managed beans of each scope:
  <af:commandButton text="commandButton 1" id="cb1"
     action="go" binding="#{backingBeanScope.BackBean.button}">
  </af:commandButton>

  <af:commandButton text="commandButton 1" id="cb2"  
    binding="#{viewScope.ViewBean.button}"/>

  <af:commandButton text="commandButton 1" id="cb3"  
    binding="#{pageFlowScope.FlowBean.button}"/>

The bean class has the button attribute and testString attribute that signals whether the button is assigned:
  private RichCommandButton button;
  
  public void setButton(RichCommandButton button)
  {
    this.button = button;
  }

  public RichCommandButton getButton()
  {
    return button;
  }

  public String getTestString()
  {
    if (this.button == null)
      return "The button is not assigned";
    else
      return "The button is assigned";
  }



When we press cb1 we go to the return activity and the finalizer gets executed:

public static String resolveExpression(String expression)
 {
   FacesContext fc = FacesContext.getCurrentInstance();
   return (String) fc.getApplication().evaluateExpressionGet(fc, expression,
                                                    String.class);
 }

public void theFinalizer() 
{
  //Just to have test access to the managed beans
  //and to be sure we work with the same instances
  System.out.println(resolveExpression("#{pageFlowScope.FlowBean.testString}")+
                     " " + resolveExpression("#{pageFlowScope.FlowBean.button}"));
  System.out.println(resolveExpression("#{viewScope.ViewBean.testString}")+
                     " " + resolveExpression("#{viewScope.ViewBean.button}"));
  System.out.println(resolveExpression("#{backingBeanScope.BackBean.testString}")+
                     " " + resolveExpression("#{backingBeanScope.BackBean.button}"));
}




Run the application, press the cb1 button and see the following in the system log:

The button is assigned RichCommandButton[UIXFacesBeanImpl, id=cb3]
The button is assigned RichCommandButton[UIXFacesBeanImpl, id=cb2]
The button is assigned RichCommandButton[UIXFacesBeanImpl, id=cb1]

Everything seems to be ok. The task flow is finished and in the finalizer we work with correct managed bean instances. In this test the task flow is finished correctly using Return activity.
And now let's abandon our task flow - just go away from the page the task flow is put on. The finalizer is executed as well, and have a look at system out:

The button is assigned RichCommandButton[UIXFacesBeanImpl, id=cb3]
The button is assigned RichCommandButton[UIXFacesBeanImpl, id=cb2]
The button is not assigned 

This means that we work with different instance of the backingBeanScope.BackBean! In case of abounded task flow the controller don't see correct backingBeanScope in the finalizer, it is empty and the controller create new instance of the BackBean. At the same time pageFlowScope and viewScope work perfect. So, be careful when you use backingBean scope managed beans within task flows, especially when you access them in finalizers. But in any case you can use the same trick described in the previous post.

That's it!


Get access to the BackingBeanScope of the inner TaskFlow

Let's say we have a page with a region and some bounded task flow inside the region. The task flow has some backingBean scope managed bean:

 <managed-bean id="__1">
  <managed-bean-name id="__2">BackBean</managed-bean-name>
  <managed-bean-class id="__3">view.BackBean</managed-bean-class>
  <managed-bean-scope id="__4">backingBean</managed-bean-scope>
 </managed-bean>

We need to have an access to this bean outside of the task flow. For example we need to disable a button on the main page depending on some property of the BackBean.  We can do the following:

  // taskflow is <taskFlow id="taskflowdefinition1" from our pageDef
  public Map getBackingBeanScope4TaskFlow(String taskflow) 
  {
    Map resultMap = null;
    
    //We need the full name of our taskflow
    DCBindingContainer dcb =
        (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
    DCTaskFlowBinding dfb = (DCTaskFlowBinding) dcb.findExecutableBinding(taskflow);
    String fullName = dfb.getFullName();                                 
  
    //Get the provider
    BackingBeanScopeProviderImpl bbProvider =
            ((BackingBeanScopeProviderImpl)AdfFacesContext.getCurrentInstance().getBackingBeanScopeProvider());
    
    //Left parenthesis
    bbProvider.begin(fullName);
    
    //Now the current backing bean scope is the scope of our task flow
    try {
       resultMap = bbProvider.getCurrentScope();
    } finally {
    //Right parenthesis
        bbProvider.end();
    }
    
    return resultMap; 
  }

  public Object getBackBean() 
  {
    return getBackingBeanScope4TaskFlow("taskflowdefinition1").get("BackBean"); 
  }
  

 Of-course using this technique is a little bit against the encapsulation concept - we should not have access to the details of the inner/child objects. We have to be aware of that. And for our use-case it'd be probably better to use contextual events or something else. But in any case we know how to do the trick, and Knowledge is Power :).

That's it!

21 Apr 2012

How to avoid validation of immediate inputs

Introduction
Setting the immediate attribute of JSF/ADF command components to true is not the silver bullet that can help you to avoid validation. This is explained in the Understanding the JSF Immediate attribute post. If you have immediate input controls on the same form, these controls are going to be validated in any case. But immediate input controls could be quite useful as it was shown in the Update model in the ValueChangeListener post.  So, let's say you have immediate required inputText and immediate commandButton (some "Cancel" button) on the same form. Leaving the inputText empty will cause a validation error when the commandButton is pressed.


This post is showing a technique of using some "super" Immediate button, that can help us to really avoid validation in any case. So, our "super" Immediate button looks like this:

   <af:commandButton text="Cancel" id="cb1" 
                      actionListener="#{TestBean.buttonActionListener}"        
                      immediate="true"
                      action="someAction">
                      
      <af:clientAttribute name="superImmediate" value="#{true}"/>                
      <af:clientListener method="catchActionEvent" type="action"/>
      <af:serverListener type="customButtonAction"
                         method="#{TestBean.customButtonActionListener}"/>

    </af:commandButton>                   


And a little bit of Java Script:
   <af:resource type="javascript">  
     
     // This hack avoids client-side validation for the "superImmediate"
     // commandComponents
     AdfActionEvent.prototype.isValidationNeeded = function()
       {
         return !this.getSource().getProperty("superImmediate");
       } 
       
    // Action event will cause server-side validation at the
    // Apply Request Values phase (for immediate button).
    // So we'll get validation error even if the client-side validation is suppressed
    // We need to catch the original Action event, cancel it and replace with our
    // custom event customButtonAction.
    function catchActionEvent(evt){
      AdfCustomEvent.queue(evt.getSource(), 'customButtonAction' , null, true); 
      evt.cancel();                  
    }   
    
   </af:resource>


And finally we need to emulate an Action Event in our customButtonActionListener in order to get the button's actionListener executed and the action processed:

  public void customButtonActionListener(ClientEvent clientEvent) {
      UIXCommand cb = (UIXCommand) clientEvent.getComponent();
      cb.broadcast(new ActionEvent(cb));
  }


That's it!