31 Dec 2013

Working with Web Camera in ADF application

In this post I am going to show how we can enrich an ADF application with features like taking pictures with a Web camera.  In order to get an access to the camera's stream and in an effort to show taken pictures we will use HTML5 tags video and canvas.  Basically, my html code looks like the following:

<table  border="1" width="900" align="center">
  <tr align="center">
    <td>
     Web Camera
     <div id="camera"
           style="height:300px; width:400px; border-style:solid;">               
      <video id="video" width="400" height="300"></video>         
     </div> 
     </td>
    <td>
      Photo Frame
      <div id="frame"
           style="height:300px; width:400px; border-style:solid;">
        <canvas id="canvas" width="400" height="300" ></canvas>   
      </div>
    </td>  
  </tr>
  <tr align="center">
    <td colspan="2">
     <button id="snap" onclick="snap()">Snap Photo</button>                  
    </td>
  </tr>
</table>




And it looks like this:


On the left side we are going to show a video stream, captured from a Web camera, and on the right side we are going to show taken pictures. In order to get it all working we need some java script:
  <script type="text/javascript">
           
      var video = document.getElementById("video");
          videoObj = { "video": true },
          canvas = document.getElementById("canvas"),
          context = canvas.getContext("2d"),
          frame = document.getElementById("frame"); 
          errorBack = function (error) {
            alert(error); 
            }; 
                           
      
       //This function is going to be invoked on page load
       function setupCamera() 
       {
         if(navigator.getUserMedia) { 
            navigator.getUserMedia(videoObj, standardVideo, errorBack);
          } else if(navigator.webkitGetUserMedia) { 
            navigator.webkitGetUserMedia(videoObj, webkitVideo, errorBack);
          }
          else if(navigator.mozGetUserMedia) { 
            navigator.mozGetUserMedia(videoObj, mozillaVideo, errorBack);
          }             
         
       }
      
      
       //Different functions for different browsers
       standardVideo = function(stream) {
                    video.src = stream;
                    video.play(); } 
      
       webkitVideo = function(stream){
                    video.src = window.webkitURL.createObjectURL(stream);
                    video.play(); }
                   
       mozillaVideo = function(stream){
                    video.src = window.URL.createObjectURL(stream);
                    video.play(); }

       //onClick for the snap button
       function snap() 
         {
           //Take a picture from video stream
           context.drawImage(video,0,0,400,300);
          
           //Add a css3 class style to get a "sliding" effect
           canvas.className="animated";              
          
           //Add s listener to remove "animating" class style
           //at the end of animation
           canvas.addEventListener("animationend", animationEnd, false);
        }
        
       function animationEnd(e)
       {//Finish the animation
        canvas.className="normal";
       
        //Set up taken picture as div's background 
        frame.style.backgroundImage='url('+canvas.toDataURL("image/png")+')'; 
       }
      
      
       //on load
       window.addEventListener("DOMContentLoaded",setupCamera(), false);
 </script>




We use some CSS3 animation in order to get a "slide" effect. So, the picture is going to slide from the video stream to the Photo Frame:

 <style type="text/css">
  
   @keyframes slide
    {
     from {left:-440px;} 
     to {left:0px;}
    }

   .animated
   {      
     position:relative;
     animation:slide 0.5s ease-in;
     -webkit-animation: slide 0.5s ease-in;               
   }
  
   .normal
   { 
   }

</style>

So, you can play with the result of our work here.

Since the ADF framework gets confused about mixing pure html tags with ADF components, we're going to access to the html page, containing video and canvas tags via af:inlineFrame.  An ADF page fragment should look like this:

<af:panelStretchLayout id="psl1">
   
  <f:facet name="bottom">
   <af:panelGroupLayout layout="horizontal" id="pgl1"> 
     <af:commandButton text="Submit" id="cb1" action="submit">
       <af:clientListener type="action" method="takePicture"/>
       <af:serverListener type="takePicture"
                          method="#{TakePictureBean.takePicture}" />
     </af:commandButton>
     <af:commandButton text="Cancel" id="cb2" action="cancel"/>
   </af:panelGroupLayout> 
  </f:facet>
   <f:facet name="center">
    <af:inlineFrame id="iframe" 
                    source="Snapshot.html" 
                    styleClass="AFStretchWidth"
                    />
  </f:facet>
</af:panelStretchLayout>


Let's pay attention to the Submit button. Actually, when we click this button, we're going to grab taken picture, send the image to the server and set it as a value of a BLOB entity attribute. So, let's look at the client side first. The code of the button's client listener:
function takePicture(actionEvent)
{ //We are doing all this stuff just in order
  // to get access to the inlineFrame's content
  var source = actionEvent.getSource();
  var ADFiframe = source.findComponent("iframe");
  var iframe = document.getElementById(ADFiframe.getClientId()).firstChild;
  var innerDoc = (iframe.contentDocument) ? 
                  iframe.contentDocument : 
                  iframe.contentWindow.document;

  //Finally we can work with our canvas,
  //containing taken picture
  var canvas = innerDoc.getElementById("canvas");
  
  //Convert the image into the DataURL string and
  //send it to the server with a custom event 
  AdfCustomEvent.queue(source, "takePicture",
                       {picture : canvas.toDataURL("image/png")}, true);
}



On the server side we're going to listen to the takePicture event. So, the code of the server listener:

public void takePicture(ClientEvent clientEvent)
  throws SQLException
{  

  //Convert the image into an array of bytes
  String picture  = (String) clientEvent.getParameters().get("picture");
  String prefix = "base64,";
  int startIndex = picture.indexOf(prefix) + prefix.length();
  byte[] imageData = Base64.decodeBase64(picture.substring(startIndex).getBytes());

  //Find an attribute binding pointing to a BLOB entity attribute
  BindingContext bc = BindingContext.getCurrent();
  DCBindingContainer dcb = (DCBindingContainer) bc.getCurrentBindingsEntry();
  AttributeBinding attr = (AttributeBinding)dcb.getControlBinding("Image");
  
  //And set an attribute's value  
  attr.setInputValue(new BlobDomain(imageData));          
}

You can download a sample application for this post. It requires JDeveloper 11gR2.

That's it!








22 Dec 2013

Hot Redeployment of ADF Libraries with JRebel

One of the most powerful features of JDeveloper 11gR2 and 12c is the ability of hot redeployment of classes and resources to the integrated WebLogic server. And sometimes it really works, saving a lot of developers' time. You just need to make your changes, rebuild the project, refresh the browser and you are happy.  There is no full redeployment of the entire application.
But it doesn't work with ADF libraries. Any modern large ADF application is split into an number of ADF libraries and developers work mostly with code of those libraries. And in order to get their changes applied, developers need to redeploy a library and restart an application. There is pretty powerful tool JRebel, which can make working with ADF libraries much faster and easier. It can be installed as JDeveloper's plugin and it allows hot redeployment of library's classes and resources likewise classes and resources of the target running project. Even better. With JRebel we don't have to rebuild the entire project in case of making changes to class files, we can just rebuild corresponding classes and that's it.  
The installation process is pretty easy and well described in this manual. Once the plugin has been installed, we have to generate JRebel configuration files (rebel.xml) for the main target project which we actually run and for each project which is going to be deployed as an ADF library:
 

For example, for the ViewController project it will generate something like this:
<?xml version = '1.0' encoding = 'UTF-8'?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd"
     xmlns="http://www.zeroturnaround.com">  
 <classpath>   
  <dir name="/Users/Fedor/jdeveloper/mywork/HotDeployApp/ViewController/adfmsrc"/>
  <dir name="/Users/Fedor/jdeveloper/mywork/HotDeployApp/ViewController/classes">
     <include name="**/*.class"/>
     <exclude name=".wlsjsps/**"/>
  </dir>
  <dir name="/Users/Fedor/jdeveloper/mywork/HotDeployApp/ViewController/src">
    <exclude name="**/*.java"/>
  </dir>
 </classpath>
     
 <web>
   <link target="/">
     <dir name="/Users/Fedor/jdeveloper/mywork/HotDeployApp/ViewController/public_html"/>                  
   </link>
 </web>
</application> 

Note, that absolute paths are used, which is not feng shui. We have to manually edit generated rebel.xml and use a special placeholder ${workspace.path}:

<?xml version = '1.0' encoding = 'UTF-8'?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd"
     xmlns="http://www.zeroturnaround.com">  
 <classpath>   
  <dir name="${workspace.path}/ViewController/adfmsrc"/>
  <dir name="${workspace.path}/ViewController/classes">
     <include name="**/*.class"/>
     <exclude name=".wlsjsps/**"/>
  </dir>
  <dir name="${workspace.path}/ViewController/src">
    <exclude name="**/*.java"/>
  </dir>
 </classpath>
     
 <web>
   <link target="/">
     <dir name="${workspace.path}/ViewController/public_html"/>                  
   </link>
 </web>
</application>


Since the ViewController project is a web project, there is a <web> tag in the file. Actually the same tag is going to be generated for ADF library projects as well, if those projects are web projects containing some task flows. The important thing is that we don't need the <web> tag in the rebel.xml file for ADF library projects. It doesn't work. We have to manually move the public_html dir tag to the <classpath> section:

<?xml version = '1.0' encoding = 'UTF-8'?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd"
    xmlns="http://www.zeroturnaround.com">
   <classpath>
      <dir name="${workspace.path}/TaskFlow/classes">
         <exclude name=".wlsjsps/**"/>
      </dir>
      <dir name="${workspace.path}/TaskFlow/src">
         <exclude name="**/*.java"/>
      </dir>
      <dir name="${workspace.path}/TaskFlow/public_html"/>      
   </classpath>
   <!--
   <web>
      <link target="/">
         <dir name="${workspace.path}/TaskFlow/public_html"/>
      </link>
   </web>-->
</application>   

And in order to get it all working, we need to enable JRebel in JDeveloper before starting the integrated WebLogic server:
And add the following string to Java options:

-noverify -Drebel.adf_core_plugin=true -Drebel.adf_faces_plugin=true -Dworkspace.path=/Users/Fedor/jdeveloper/mywork/HotDeployApp


That's it!

29 Nov 2013

What you may need to know about ADF Faces selection components and PPR

In this post I'm going to uncover a pitfall to watch for when using ADF Faces selection components such as af:selectBooleanCheckbox, af:selectOneChoice, af:selectOneRadio, af:selectManyCheckbox, af:selectBooleanRadio, af:selectManyChoice, etc. 

Let's consider a very simple example:
<af:inputText label="Label 1" id="it1"
              autoSubmit="true"                            
              />
<af:panelGroupLayout id="pgl1">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1"
                            />
</af:panelGroupLayout>
<af:outputText value="#{TestBean.someBooleanValue}" 
               id="ot1" partialTriggers="it1"/>
 
The default value of TestBean.someBooleanValue is true, so it's going to look like this:

SelectBooleanCheckbox and OutputText are partial targets of the InputText, though the InputText actually does nothing except submitting a request to the server.
For some reason we want to hide the PanelGroupLayout. Let's set the rendered attribute of the PanelGroupLayout to false:
<af:inputText label="Label 1" id="it1"
              autoSubmit="true"                            
              />
<af:panelGroupLayout id="pgl1" rendered="false">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1" 
                            />
</af:panelGroupLayout>
<af:outputText value="#{TestBean.someBooleanValue}" 
               id="ot1" partialTriggers="it1"/>
and refresh the page:

Let's enter a value of the InputText and press Tab:

We can see that TestBean.someBooleanValue got false. Why?
Since SelectBooleanCheckbox and OutputText are partial targets of the InputText, the framework is going to process these components during the request along with the InputText.
Very roughly the framework does the following for each component participating in a request:
 if (component.isRendered() && component.getSubmittedValue()!=null) 
  processComponent(component);
else
  skipComponent(component);
   

  
The root of the evil is in the getSubmittedValue() method. Since selectBooleanCheckbox is not visible (it is even not rendered because it is laying on a panel which is not rendered), there is no any submitted value for it. It just can't be there. But getSubmittedValue() never returns null for selection components. In case of  selectBooleanCheckbox it returns false instead of null. If it was an inputText component, it would return null for sure.
The second problem is that the framework checks whether the component is rendered or not, but it doesn't pay attention to its parents.
So, in our case, we have a SelectBooleanCheckbox with "false" submitted value participating in the request. For sure, it will set its model value to false.

How to avoid this pitfall?
We can either set rendered attribute of the selectBooleanCheckbox to false:
<af:panelGroupLayout id="pgl1" rendered="false">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1" 
                            rendered="false"
                            />
</af:panelGroupLayout>


or set the PanelGroupLayout as a partial target of the InputText:
<af:panelGroupLayout id="pgl1" rendered="false"
                     partialTriggers="it1">
  <af:selectBooleanCheckbox text="selectBooleanCheckbox 1"
                            id="sbc1"
                            value="#{TestBean.someBooleanValue}"
                            partialTriggers="it1" 
                            />
</af:panelGroupLayout>
That's it!

17 Nov 2013

Validating dates with af:validateDateTimeRange validator

Sometimes we need to validate that the date entered in an af:inputDate component is within some range. ADF Faces provides a very convenient approach for that - af:validateDateTimeRange. It is very easy to use and it could look like this in a jspx code:
<af:inputDate value="#{dateValue}">
   <af:validateDateTimeRange minimum="#{minimumValue}"
        maximum="#{maximumValue}"
        messageDetailNotInRange="The date value {1} is not in the range {2} - {3}"
        /> 
</af:inputDate>
But there is some pitfall to be aware.

Let's say we've got some ViewObject with three oracle.jbo.domain.Date attributes - ValueDate, MaturityDate and PaymentDate:


We put correspondent inputDate components on a page and set up autoSubmit attribute for ValueDate and MaturityDate:

 
We need to keep the value of PaymentDate within the range between ValueDate and MaturityDate. So, w're going to add a validator to the PaymentDate:

<af:inputDate value="#{bindings.PaymentDate.inputValue}"
              label="#{bindings.PaymentDate.hints.label}"
              required="#{bindings.PaymentDate.hints.mandatory}"
              columns="#{bindings.PaymentDate.hints.displayWidth}"
              shortDesc="#{bindings.PaymentDate.hints.tooltip}"
              id="id3">
  <f:validator binding="#{bindings.PaymentDate.validator}"/>
  <af:convertDateTime pattern="#{bindings.PaymentDate.format}"/>
  
  <af:validateDateTimeRange minimum="#{bindings.Valuedate.inputValue.value}"
                            maximum="#{bindings.Maturitydate.inputValue.value}"
                            />
  
</af:inputDate>


And let's test it now:

The error message is absolutely correct since PaymentDate is less than ValueDate.



The value of PaymentDate is equal to ValueDate and it is within the range ValueDate-MaturityDate. So there is no any validation exception.



Ooops!  The value of PaymentDate is equal to MaturityDate and it is within the range ValueDate-MaturityDate. But the validation exception is fired.
We can figure out the reason of this strange behavior in the specification of the maximum attribute of the af:validateDateTimeRange:

"... When binding to Date objects, it is advised to create the Date object with maximum value for any date-time components that aren't displayed (usually hours, minutes, seconds, milliseconds) to allow the largest range of values to be accepted."

So, in order to get it working correctly, it is advised to specify in the maximum attribute of the validateDateTimeRange something like this: "5/24/2012 23:59:59.999".

Let's give it what it wants. In our ViewObject we created a transient oracle.jbo.domain.Date attribute LimitMaturityDate with the following value expression:

 The expression actually adds a day (24 hours) to the Maturitydate and subtracts a milisecond, so we're going to get "Maturitydate 23:59:59.999". The new java.sql.Timestamp object is going to be converted into the oracle.jbo.domain.Date by the framework. And let's use this transient attribute as a maximum for the af:validateDateTimeRange:
  <af:validateDateTimeRange minimum="#{bindings.Valuedate.inputValue.value}"
                            maximum="#{bindings.LimitMaturityDate.inputValue.value}"
                            />


And if we test it again:


It will work fine.


That's it!

20 Oct 2013

Oracle ADF Performance Tuning War Stories

In this post I would like to thank everyone, who managed to attend the session "Oracle ADF Performance Tuning War Stories" on ADF EMG Sunday at Oracle Open World 2013. I was proud to speak at this session along with Stephen Johnson and Frank Houweling. I hope this session will help ADF developers to feel themselves more confidently and understand better how to tune their applications and JVM.

The slides of my presentation can be found on the SlideShare:
http://www.slideshare.net/euegenefedorenko/oow2013-ef-final43

Since the presentation contains a lot of animations, perhaps it would be more convenient to download the pptx source:
http://adf-practice.googlecode.com/files/OOW2013_EF_Final_4_3.zip

That's it!

Developing Web Applications with Oracle ADF Essentials

Since Oracle ADF Essentials has been released it changed the mind of Java EE developers across the world. They started to look at ADF as at the serious alternative to a set of heterogeneous frameworks and technologies the used to build Web applications. They realized that this single powerful framework could provide them with almost everything they needed, and absolutely for free!
Nowadays there are a number of books and various resources providing plenty of information about Oracle ADF. But what could be chosen as a starting point?

A few months ago I was proud to be a technical reviewer of a new ADF book Developing Web Applications with Oracle ADF Essentials by Sten Vesterli. The book has been published at the end of August 2013.



Being not a thick book, this resource manages to show the full power of Oracle ADF Essentials on 250 pages only, and the reading is really enjoyable and easy. I would recommend this book as an excellent starting point for those who want to know what Oracle ADF is all about.

That's it! 

17 Sep 2013

Populating tables with method iterator

Sometimes it can be useful to provide the data collection for a table component by some custom method in the service layer. An example of this technique can be found here. In this case we use methodAction to invoke the method and methodIterator to represent method's return value as a data collection for the table.
Let's say in our EmployeesView view object we've got a method returning some secondary custom ViewObject's row set:
   public RowSet getManagers(){   
    RowSet rs = (RowSet) findByViewCriteria(getViewCriteria("ManagersCriteria"), -1,  
                              ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES);  
    return rs;       
  }


In the PageDef file we've got methodAction invoking this method:
<methodAction id="getManagers" RequiresUpdateModel="true"
    Action="invokeMethod" MethodName="getManagers"
    IsViewObjectMethod="true"
    DataControl="AppModuleDataControl"
    InstanceName="data.AppModuleDataControl.EmployeesView"
    ReturnName="data.AppModuleDataControl.methodResults.getManagers_AppModuleDataControl_EmployeesView_getManagers_result"
    />


The methodIterator:
    <methodIterator Binds="getManagers.result"
                    DataControl="AppModuleDataControl" RangeSize="25"
                    id="getManagersIterator"/>
 
And the tree definition:
    <tree IterBinding="getManagersIterator" id="VEmployees1">
      <nodeDefinition DefName="com.cs.blog.appmethoditerator.model.EmployeesView"
                      Name="VEmployees10">
        <AttrNames>
          <Item Value="EmployeeId"/>
          <Item Value="FirstName"/>
          <Item Value="LastName"/>
        </AttrNames>
      </nodeDefinition>
    </tree>


The table which is based on this structure looks like this:


Everything seems to be fine. But, actually, it's not. If we try to sort our data in the table, there will be no any effect. The getManagers method is going to return new row set without any bothering about applied sort criteria. Actually the framework applies the sort criteria to the EmployeesView view object, but not to the row set, returning by the getManagers method. On the other hand, this secondary row set is populated by some internal helper view object, which is called finder view object. And this finder view object has no any idea about changed OrderBy clause. We can fix that overriding ViewObject's method createFinderVO:

  protected ViewObjectImpl createFinderVO(String suffix) 
  {
    ViewObjectImpl vo = super.createFinderVO(suffix);
    //Get OrderBy clause from the parent VO and set it up for the finder VO 
    vo.setOrderByClause(getOrderByClause());
    return vo;                                                           
  }


And now everything works really fine.

That's it!

31 Aug 2013

ADF EMG Sunday at Oracle Open World 2013. Must Have on Your Schedule.

Going to attend OOW 2013? Looking for more ADF sessions? There is great news for you!

This year ADF Enterprise Methodology Group has arranged again a full day of ADF sessions. The room has been kindly donated by Oracle Development Tools Users Group (ODTUG).
Come to Moscone West 2003 on Sunday, September 22nd and dive deeply into solid ADF content! ADF only, and nothing else!
The full session list comes up with the following agenda:
  • Session 1. 8:00am-9:00am[UGF7001] Oracle ADF Task Flows Beyond the 10-Minute Demo.  John King

  • Session 2.  9:15am-10:15am. [UGF9898] Oracle on Your Browser or Phone: Design Patterns for Web and Mobile Oracle ADF Applications. Floyd Teter & Lonneke Dikmans

  • Session 3. 10:30am-11:30am. [UGF2737] ADF Performance Tuning War Stories. Stephen Johnson, Frank Houweling, Eugene Fedorenko

  • Session 4. 11:45am-12:45pm. [UGF9900] Top 10 Web App Vulnerabilities, and Securing Them with ADF. Brian Huff

  • Session 5. 2:15pm-3:15pm. [UGF9860] Worst Practices When Developing an ADF Application. Paco van der Linden & Wilfred van der Deijl

  • Session 6. 3:30pm-4:30pm. [UGF9908] WebCenter & ADF - Responsive and Adaptive Design for Desktop, Mobile & Tablet. John Sims 
All sessions are available at the content catalog and the full list of ADF content at OOW 2013 is summarized down here.

Before this great forum, on Saturday 21st, ADF EMG is going to host some warming-up event with fajitas, margaritas, cold Anchor Steam and conversations on ADF.  


See you in San Francisco at OOW 2013!

19 Aug 2013

About the Scope of EntityDef and ViewDef

Recently I was hunting an interesting bug. The bug was pretty confusing for users because it seemed that some data can be transfered across user sessions. The reason of the bug was very common mistake. Sometimes ADF developers consider Entity definitions (EntityDef) and View definitions (ViewDef) as session scoped objects. They change properties of EntityDefImpl and ViewDefImpl instances, or, which is more likely, properties of AttributeDefImpl and forget that these changes effect all users sessions. There is only one instance of particular EntityDefImpl per application instance. The same is correct for ViewDefImpl as well. 
Let's consider a simple task flow:
There is view activity BrowseView representing data in a table and allowing users to create new records. The default activity of the task flow is some method call setDefaultLastName. If we look at its code we'll see the following:

  //The method sets user's last name as
  //the default value of the "LastName" attribute 
  public void setDefaultLastName(String lastName) 
  {
    //Get Entity Definition
    EntityDefImpl ed = getDefinitionObject();
    
    //Find an attribute definintion 
    AttributeDefImpl ad = (AttributeDefImpl) ed.findAttributeDef("LastName");
    
    //Set the default value
    ad.setDefaultValue(lastName);
  }



So, when users start this task flow, the default value of the "LastName" attribute of the table becomes their last name. And, after that, when they create new records the value of the "LastName" is populated with their name. Cool! User, whose name is Stogova, entered the task flow, started to create new records and she is happy. So far. Another user, whose name is Smith, did the same. Stogova is still creating new records at BrowseView, and one moment she is realizing that she has become Smith. Cool!

The correct way to implement this use case is to define some groovy expression as the default value of the "LastName" attribute. Something like this:

adf.userSession.userData.lastName


And, for sure, user's last name should be passed somehow to the business service layer. For example, by invoking the following method:

  public void setupUserLastName(String lastName) 
  {
    ApplicationModule am = getApplicationModule();
    am.getSession().getUserData().put("lastName", lastName);   
  }


That's it!

31 Jul 2013

URL Task Flow Call with HTTP POST Method

As we know a bounded task flow can be invoked by some URL either directly from a browser or from some external application. This feature is enabled if task flow's property "URL invoke" is set to "url-invoke-allowed" and it is commonly used in integration projects. Usually clients (or invokers) use HTTP GET method and pass their parameters in the URL. Let's consider some simple task flow with one required input parameter:
  <task-flow-definition id="task-flow-definition">    
    <input-parameter-definition id="__23">
      <name id="__24">userName</name>
      <value id="__67">#{requestScope.userName}</value>
      <class id="__63">java.lang.String</class>
      <required/>
    </input-parameter-definition>    
    ...


 The task flow can be invoked by the URL like this

http://127.0.0.1:7101/TestApp/faces/adf.task-flow?adf.tfId=task-flow-definition&adf.tfDoc=/WEB-INF/task-flow-definition.xml&userName=xammer


The client uses a simple html form to construct this GET request:

<html>
  <head>    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  </head>
  <body>
   <form action="http://127.0.0.1:7101/TestApp/faces/adf.task-flow">
   <input type="hidden" name="adf.tfId" value="task-flow-definition"/>  
   <input type="hidden" name="adf.tfDoc" value="/WEB-INF/task-flow-definition.xml"/>  
   <label>     
        User Name 
      <input type="text" name="userName" value="xammer"/>  
   </label>
      <input type="submit" value="Submit"/>
    </form>
    </body>
</html> 


And it looks like this:

Everything is ok. So far. It works fine for R1 and for R2 as well.
Some clients prefer to use HTTP POST method, and moreover this is their requirement:

<html>
  <head>    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  </head>
  <body>
   <form action="http://127.0.0.1:7101/TestApp/faces/adf.task-flow" method="POST">
   <input type="hidden" name="adf.tfId" value="task-flow-definition"/>  
   <input type="hidden" name="adf.tfDoc" value="/WEB-INF/task-flow-definition.xml"/>  
   <label>     
        User Name 
      <input type="text" name="userName" value="xammer"/>  
   </label>
      <input type="submit" value="Submit"/>
   </form>
   </body>
</html> 

  

And it works fine as well. The URL in this case is going to look like this:

http://127.0.0.1:7101/TestApp/faces/adf.task-flow


All other necessary information like task flow id and parameter value is inside POST request. But the problem is that it works fine for R1 only. If we try it out on R2 we will get the following:

ADF_FACES-30179:For more information, please see the server's error log for an entry beginning with: The UIViewRoot is null. Fatal exception during PhaseId: RESTORE_VIEW 1.

Why? Because of that:

oracle.adfinternal.controller.application.InvokeTaskFlowException: ADFC-02006: A task flow ID is not found in the URL.
    at oracle.adfinternal.controller.util.UrlParams.getTaskFlowInfo(UrlParams.java:144)
    at oracle.adfinternal.controller.application.RemoteTaskFlowCallRequestHandler.invokeTaskFlowByUrl(RemoteTaskFlowCallRequestHandler.java:84)
    at oracle.adfinternal.controller.application.RemoteTaskFlowCallRequestHandler.doCreateView(RemoteTaskFlowCallRequestHandler.java:63)

All necessary data included task flow id which is supposed to be passed inside POST request is lost. Why?  Because of "loopback". If we discover requests sent from the browser to the server on clicking the Submit button we'll see the following:

Instead of one we have two requests. And the first one (POST) is ours. Exploring the response for this request we can see the following:


 So, instead of sending the "honest" response, the server sends some "loopback" script which generates "window id" and sends the following GET request with generated window id. Cool! But all post data is gone. The GET request is absolutely empty.
Fortunately, the framework doesn't generate any "loopbacks" if the initial POST request has already some "window id". So, the workaround for our case is to develop a servlet filter, setting the "window id" attribute for our request:
public void doFilter(ServletRequest servletRequest,
                     ServletResponse servletResponse,
                     FilterChain filterChain)
  throws IOException, ServletException
{
  HttpServletRequest r = (HttpServletRequest) servletRequest;
  HttpSession s = r.getSession();
  
  //May be this is not an initial request and window id has been generated earlier
  //We want all the following requests to work with the same window id 
  //For our use-case this is ok    
  String windowID = (String) s.getAttribute(_WINDOW_ID_KEY);
  if (windowID == null)
  {
    String pathInfo = r.getPathInfo();
    //This is an initial POST request to get access to the task flow
    if (("/adf.task-flow").equals(pathInfo) &&
        "POST".equals(r.getMethod()))
    {
      windowID = WINDOW_ID;
      //Save window id in the session 
      s.setAttribute(_WINDOW_ID_KEY, windowID);
    }

  }

  //Setup attribute for the request
  //This will prevent generating of the loopback
  if (windowID != null)
    r.setAttribute(_WINDOW_ID_KEY, windowID);

  filterChain.doFilter(servletRequest, servletResponse);
}

private static final String __WINDOW_MANAGER_KEY = RichWindowManager.class.getName();
private static final String _WINDOW_ID_KEY = __WINDOW_MANAGER_KEY + "#WINDOW_ID";  
private static final String WINDOW_ID = "wextflow";

Notice, that this filter should stand before "trinidad" filter in the filter chain:
  <filter>
    <filter-name>ExtPostFilter</filter-name>
    <filter-class>com.cs.fusion.core.view.filter.ExtPostFilter</filter-class>
  </filter> 
  <filter>
    <filter-name>trinidad</filter-name>
    <filter-class>org.apache.myfaces.trinidad.webapp.TrinidadFilter</filter-class>
  </filter>
  <filter>
    <filter-name>ServletADFFilter</filter-name>
    <filter-class>oracle.adf.share.http.ServletADFFilter</filter-class>
  </filter>

That's it!

19 Jul 2013

Passivation and Activation of View Objects with Transient Attributes

Within passivation/activation cycle of application modules the framework passivates and activates view objects as well. Usually the framework saves information about VO's state, current row, bind variables values and such. But not the data. The VO's query is going to be re-executed and the data is going to be re-fetched after activation of the view object. In most cases the query execution is not performed during or right after the activation phase, but is deferred until the view object is really used. This behavior is quite logical. Let's assume that our application consists of several pages representing data of different view objects. If we send requests to the server from the same page we are going to get VOs executed that are used on that page only. All other view objects, used on other pages, are going to be passivated and activated as well. But they are not going to be re-executed until we ask the framework to do that by navigating to the particular page. And that's cool! It means that we don't perform unnecessary query executions and we don't waste our memory. But there are some cases when the framework performs VO's query execution during the activation phase not bothering whether we really use the VO.
One of these cases is about using of transient VO's attributes. The common recommendation is to not passivate such attributes. But sometimes transient attributes are used to store some custom data and passivation/activation mechanism is used as a convenient way to save this data and keep it alive. Be careful with this approach. If any values of transient attributes are passivated, then the framework will execute the query during the activation of the view object.

Let's consider a sample application with two pages - Employees and Departments.

We use read-only SQL-based view objects browsing data on both pages:



So all VO's attributes are transient and we're not going to passivate them. Application module pooling is disabled:



 Exploring the request sent from the Employees page with ODLA we can see the following:



There is no any query execution within application module activation phase. The VEmployees query has been executed in prepare model phase as it was expected.
And now let us change the passivate parameter of one of the VDepartments attributes:

 
I am going to start the application with the Departments page in order to get the VDepartments VO executed and after that navigate to the Employees page. Sending next requests from the Employees page (for example sorting the table) we are going to get the following picture: 


It is obvious that besides execution of VEmployees query the framework executes VDepartments as well. And VDepartments is executed during the activation of the application module. Do we really need that? We are wasting CPU resources for unnecessary query executions and wasting memory to store query collections of unused view objects. The framework demonstrates the same behavior for view objects that have any dynamic attributes and for master view objects with retain view link accessors set on. So, be aware of these features.

That's it!





30 Jun 2013

Resizing of Search and Select dialog of LOV Components

Playing with skin selectors of ADF Faces LOV components I found a new selector announced in release notes for 11.1.1.7.0 and 11.1.2.4.0 versions. The -tr-stretch-search-dialog selector enables the feature of resizing LOV's search and select dialog, allowing users to interact with this dialog in more convenient way.   

By default the feature is disabled and in order to enable it the source code in the css file for the inputListOfValues component should look like this:

af|inputListOfValues{
             -tr-stretch-search-dialog: true;
           } 


And for the inputComboboxListOfValues it should look like this:

af|inputComboboxListOfValues{
             -tr-stretch-search-dialog: true;
           } 



The cool thing is that when end user is resizing the dialog, it is going to stretch its content as well:




The sample application for this post can be downloaded here. It requires JDeveloper 11.1.2.4.0.

That's it!


22 Jun 2013

Building Custom LOV with searchContent Facet

List of Values UI components, such as inputListOfValues and inputComboboxListOfValues have a special facet searchContent. The facet is supposed to be used as an extension point which allows us to build our custom LOV's. The content of the facet is going to be rendered in the LOV's search dialog window. This feature looks pretty attractive, since there are lots of use cases when we would like to modify the search dialog content and functionality. And what is important, we basically don't want to throw away the existing LOV's search engine and build it ourselves from scratch. Our goal is to enhance a little bit the search dialog in terms of look and feel and to inherit the existing functionality.

In order to transform a LOV into the custom one, we've got to implement a couple of listeners and design the content of the searchContent facet. So, there is a LOV:

<af:inputComboboxListOfValues id="deptId"
    popupTitle="Search and Select: #{bindings.DepartmentId.hints.label}"
    value="#{bindings.DepartmentId.inputValue}"
    label="#{bindings.DepartmentId.hints.label}"
    model="#{bindings.DepartmentId.listOfValuesModel}"
    required="#{bindings.DepartmentId.hints.mandatory}"
    columns="#{bindings.DepartmentId.hints.displayWidth}"
    shortDesc="#{bindings.DepartmentId.hints.tooltip}"
    autoSubmit="true"
    
    returnPopupListener="#{LOVUtilBean.lovSearchListener}"
    launchPopupListener="#{LOVUtilBean.lovPopupListener}"
    >    


Attributes returnPopupListener and launchPopupListener refer to some managed bean methods. And the searchContent facet:
 <f:facet name="searchContent">
  <af:panelGroupLayout layout="vertical" id="pgl1">
    <af:query headerText="Search" disclosed="true"
      value="#{bindings.DepartmentId.listOfValuesModel.queryDescriptor}"
      model="#{bindings.DepartmentId.listOfValuesModel.queryModel}"
      queryListener="#{LOVUtilBean.queryListener.processQuery}"
      resultComponentId="::t1"
      saveQueryMode="hidden"
      modeChangeVisible="false" maxColumns="4"
      rows="1" id="q1"/>
             
    <af:table value="#{bindings.DepartmentId.listOfValuesModel.
                       tableModel.collectionModel}"
              var="row" rowBandingInterval="1"                        
              rowSelection="single" columnStretching="last"
              width="700px" id="t1">
      <af:column headerText="#{bindings.DepartmentId.listOfValuesModel.
                               itemDescriptors[0].name}"
                 id="c1" sortable="true"
                 sortProperty="#{bindings.DepartmentId.listOfValuesModel.
                                 itemDescriptors[0].name}"
                 align="left" width="80">
        <af:outputText value="#{row.DepartmentId}" id="ot1"></af:outputText>
      </af:column>
     
      <af:column headerText="#{bindings.DepartmentId.listOfValuesModel.
                               itemDescriptors[1].name}"
                 id="c2">
        <af:outputText value="#{row.DepartmentName}" id="ot2"/>
      </af:column>
    </af:table>
  </af:panelGroupLayout>
</f:facet>


The facet contains the standard Query+Table form, which is based on the LOV's model. Actually, we can fill free designing the content of the facet. The only requirement is that we should have one query component and one table component within the facet. But, even this restriction is based on the listener's implementation provided in this post. If you want to avoid the requirement, you can rewrite the listeners as you wish.

So, let's have a look at the listeners.
We're going to use the internal framework listener as the query listener of the Query component:
public QueryListener getQueryListener() 
{
  return queryListener;
}
private QueryListener queryListener = new InternalLOVQueryListener();
Handling the returnPopupEvent after selecting the value in the search dialog:  
public void lovSearchListener(ReturnPopupEvent returnPopupEvent)
{
  UIXInputPopup lovComponent =
    (UIXInputPopup) returnPopupEvent.getSource();

  //Looking for the table component within 
  //seacrhContent facet
  RichTable table = findTable(lovComponent);

  RowKeySet keySet = table.getSelectedRowKeys();


  if (keySet != null && keySet.size() > 0)
  {

    ListOfValuesModel model = lovComponent.getModel();
    Object newVal = model.getValueFromSelection(keySet);
    Object oldVal = lovComponent.getValue();

    //Have we selected anything new?
    //If yes, set it as a value of the LOV and update the model
    if (!ObjectUtils.equal(oldVal, newVal))
    {
      lovComponent.setValue(newVal);
      lovComponent.processUpdates(FacesContext.getCurrentInstance());
    }

  }
}

Handling the launchPopupEvent just before the search dialog is going to be rendered:
public void lovPopupListener(LaunchPopupEvent launchPopupEvent)
{
  UIXInputPopup lovComponent =
    (UIXInputPopup) launchPopupEvent.getSource();
  ListOfValuesModel model = lovComponent.getModel();

  if (model != null)
  {
    //Resetting the query component. 
    //So each time whenever the dialog is rendered the query component 
                  //is in its initial state
    QueryModel queryModel = model.getQueryModel();
    QueryDescriptor queryDesc = model.getQueryDescriptor();
    if ((queryModel != null) && (queryDesc != null))
    {
      queryModel.reset(queryDesc);
      //Looking for the query component within 
      //seacrhContent facet
      RichQuery query = findQuery(lovComponent);
      if (query != null)
        query.refresh(FacesContext.getCurrentInstance());
    }


    //If the LOV component has the searchFacet, then the framework fires this
    //event even in case of leaving the LOV component by TAB pressing. 
    //So, we have to check whether the dialog is really need to be launched.
    //And in case of exact match, we don't need any search dialog.
    Object oldVal = lovComponent.getValue();
    if (!ObjectUtils.equal(String.valueOf(oldVal),
                           launchPopupEvent.getSubmittedValue()))
    {
      List<Object> autoComplete =
        model.autoCompleteValue(launchPopupEvent.getSubmittedValue());
      
      //Do we have an exact match?
      if (autoComplete != null && autoComplete.size() == 1)
      {
        Object autoCompletedValue = autoComplete.get(0);

        Object newVal = model.getValueFromSelection(autoCompletedValue);
        lovComponent.setValue(newVal);
        lovComponent.processUpdates(FacesContext.getCurrentInstance());
        
        //We don't need to launch the dialog anymore
        launchPopupEvent.setLaunchPopup(false);
      }

    }

  }
}


The important thing is that these listeners are generic, they don't depend on any particular use-case and they don't depend on any particular LOV component. These methods can be gathered in some utility bean and used across the entire application.
The result of our work looks like this:
 
The sample application for this post can be downloaded here. It requires JDeveloper R2.

That's it! 

29 May 2013

Displaying ADF Task Flow Stack with BreadCrumbs

Let's consider a page with a region running a task flow. The task flow can invoke some internal task flow and this internal task flow can invoke its internal task flow and so on. After a few navigations of that sort our users are going to get lost. They have no idea how deep they are and how they got there. There is a special component in ADF Faces af:breadCrumbs. Usually it is used to show users their path through the application's menu, so users can know how they got to this page and how they can get back.  For example:

Let's use this component to help users in case of a deep task flow stack.In this post I am going to show how we can display the task flow stack with af:breadCrumbs component and how we can use it to stop currently running task flow and allow a user to get back.

So, we have a region:
<af:region value="#{bindings.taskflowdefinition1.regionModel}"
           text="#{RegionBean.taskFlowDisplayName}"
           id="r1"
           />
The region uses the technique described here to display the region's name. And we have af:breadCrumbs on the same page:
<af:breadCrumbs id="bc1" value="#{RegionBean.menuModel}"
                var="task">
    <f:facet name="nodeStamp">
        <af:commandNavigationItem id="comID"
                                  text="#{task.label}"
                                  actionListener="#{RegionBean.commandAction}">
             <f:attribute name="depth" value="#{task.depth}"/>
        </af:commandNavigationItem>
    </f:facet>
</af:breadCrumbs>


The value of the breadCrumbs is going to be some menu model, provided by a managed bean method:
public MenuModel getMenuModel() {

  TaskFlowLink taskFlowLink = getTaskFlowLink();
  if (taskFlowLink!=null)
    return new ChildPropertyMenuModel(taskFlowLink, "child",
            Collections.nCopies(taskFlowLink.getDepth(), 0));
  else
    return null;

}


The method uses some hierarchical data structure represented by TaskFlowLink class and converts it into a menu model using some internal helper class ChildPropertyMenuModel.  The TaskFlowLink class is our custom wrapper of the PageFlowStackEntry internal class. Furthermore, it supports the hierarchical structure by providing the child field.

public class TaskFlowLink {
  TaskFlowLink child;
  PageFlowStackEntry stackEntry;
  int depth;


  public TaskFlowLink(PageFlowStackEntry stackEntry, TaskFlowLink child, int depth) {
      this.stackEntry = stackEntry;
      this.child = child;
      this.depth = depth;
  }

  //Extracting the definition of the task flow 
  //corresponding to the stack entry
  private TaskFlowDefinition getTaskFlowDefinition() {
    MetadataService metadataService = MetadataService.getInstance();
    return metadataService.getTaskFlowDefinition(
                             stackEntry.getTaskFlowDefinitionId());
  }


  public String getLabel() {
      return getTaskFlowDefinition().getDisplayName();
  }

  public int getDepth() {
      return depth;
  }

  public TaskFlowLink getChild() {
      return child;
  }
    
}
  

And the getTaskFlowLink() method converts the page flow stack into the TaskFlowLink structure:
private TaskFlowLink getTaskFlowLink() {
  TaskFlowLink taskFlowLink = null;
  
  //Get the page flow stack for the region's view port
  PageFlowStack pfs = getViewPort().getPageFlowStack();
  
  //Convert the stack into array. Just for convenience. 
  PageFlowStackEntry[] pageFlowStack = 
      pfs.toArray(new PageFlowStackEntry[pfs.size()]);
  
  //Convert the array into the TaskFlowLink structure
  for (int i = pageFlowStack.length-1; i>=0; i--)
      taskFlowLink = new TaskFlowLink(pageFlowStack[i], 
                                      taskFlowLink, 
                                      pageFlowStack.length - i);

  return taskFlowLink;
}


The getTaskFlowLink() method uses a couple of helper methods to get access to the view port:
//Get the task flow binding
private DCTaskFlowBinding getTaskFlowBinding() {  
  BindingContainer bc = BindingContext.getCurrent().getCurrentBindingsEntry();  
  
  //taskflowdefinition1 is Id of the task flow binding in the page def file  
  //like  <taskFlow id="taskflowdefinition1" ...  
  DCTaskFlowBinding dtb = (DCTaskFlowBinding) 
    ((DCBindingContainer) bc).findExecutableBinding("taskflowdefinition1");  
  
  return dtb;  
} 

//Get the view port
private ViewPortContextImpl getViewPort() {
    DCTaskFlowBinding dtb = getTaskFlowBinding();
    return (ViewPortContextImpl) dtb.getViewPort();
}


And we are almost happy:

So, we built a menu model acceptable by the af:breadCrumbs component based on the page flow stack. That's all indeed cool, but it would be better if a user could click on a crumb and return back to the corresponding task flow. For example, clicking on "Task Flow One" I want to abandon currently running "Task Flow Two" and return back to the "Task Flow One". Moreover, I want to return exactly to the same view activity from which I got to the "Task Flow Two".
Alrighty, let's do it! Did you notice that our af:breadCrumbs has a commandNavigationItem within its nodeStamp facet. So, we're going to do something in the commandAction method when we're clicking on the item:
public void commandAction(ActionEvent actionEvent) {
    UIComponent component = actionEvent.getComponent();
    
    //Get the flow's depth in the stack 
    int depth = Integer.valueOf(component.getAttributes().get("depth").toString());
    
    //Abandon all deepper flows and return 
    //to the calling view activity 
    popTaskFlow(depth);
}


And, finally, let's have a look at the popTaskFlow method:
private void popTaskFlow(int depth) {

  //Remember current view port
  AdfcContext adfcContext = AdfcContext.getCurrentInstance();
  ViewPortContextImpl currViewPort = adfcContext.getCurrentViewPort();


  try
  {
     //Set region's view port as a current one
     //This allows task flow's finalizers to work correctly
     ViewPortContextImpl viewPort = getViewPort();
     adfcContext.getControllerState().setCurrentViewPort(adfcContext,
                                                         viewPort.getViewPortId());
     viewPort.makeCurrent(adfcContext);

     PageFlowStack stack = viewPort.getPageFlowStack();
     PageFlowStackEntry entry = null;

     //Abandon all deeper flows
     for (int i=1; i<depth; i++) {
       TASK_FLOW_RETURN_LOGIC.abandonTaskFlow(adfcContext, stack.peek());
       entry = stack.pop(adfcContext);
      }

     //Update the view port's current view activity ID to point 
     //to the view that was displayed before the popped 
     //task flow was called.         
     ActivityId newViewActivityId = entry.getCallingViewActivity();
     viewPort.setViewActivityId(adfcContext, newViewActivityId);

  }
  finally
  {//Restore current view port
   adfcContext.getControllerState().setCurrentViewPort(adfcContext, 
                                                      currViewPort.getViewPortId());
   currViewPort.makeCurrent(adfcContext);
  }

}

private static final TaskFlowReturnActivityLogic TASK_FLOW_RETURN_LOGIC 
   = new TaskFlowReturnActivityLogic();


The sample application for this post is available here. It requires JDeveloper R2.

That's it!

25 May 2013

ADF Task Flow Display Name

ADF Task Flow definition has a set of description properties:



In this post I am going to show how we can make use of these properties on example of Display Name attribute. Let's consider a region with a running task flow within it. In common case the region is dynamic one, so we don't know in advance which task flow it is going to run. Moreover, the task flaw can invoke another inner task flow, and the inner task flow can invoke its inner task flow, etc. Our goal is to show in the region's title the name of the currently running task flow. Let's assume, that all our task flow definitions have initialized property Display Name with a corresponding name as it is shown on the screenshot above. We can use a managed bean method returning a display name of the currently running task flow:

public String getTaskFlowDisplayName() {
    MetadataService metadataService = MetadataService.getInstance();

    //Get the task flow binding defined in our pageDef file
    DCTaskFlowBinding taskFlowBinding = getTaskFlowBinding();

    //Get Id of the currently running task flow
    TaskFlowId taskFlowId = taskFlowBinding.getViewPort().getTaskFlowContext().getTaskFlowId();

    //Get the definition of the currently running task flow by its Id
    //and return its display name
    return metadataService.getTaskFlowDefinition(taskFlowId).getDisplayName();
}

The getTaskFlowDisplayName() method uses some helper method getTaskFlowBinding() returning a task flow binding defined in our page definition file:

private DCTaskFlowBinding getTaskFlowBinding() {
  BindingContainer bc = BindingContext.getCurrent().getCurrentBindingsEntry();

  //taskflowdefinition1 is Id of the task flow binding in the page def file
  //like  <taskFlow id="taskflowdefinition1" ...
  DCTaskFlowBinding dtb = (DCTaskFlowBinding) ((DCBindingContainer) bc).findExecutableBinding("taskflowdefinition1");

  return dtb;
}


And a code snippet for our region should look like this:

<af:region value="#{bindings.taskflowdefinition1.regionModel}"
           text="#{RegionBean.taskFlowDisplayName}"
           id="r1"
           />


That's it!

21 May 2013

Switching Lists of Values

Some time ago I blogged about a feature when a VO's attribute can have multiple LOVs and showed how to switch to the desired LOV depending on the value of another VO's attribute. I this post I'm going to use this technique again, but I'm going to choose the LOV for the attribute depending on its value, on the value of the attribute for which the LOV is defined.
Let's consider a simple use-case: We have an input field for a currency code. When a user inputs a currency code, we have to show corresponding currency description near the input field. So, it should look like this:


The implementation of this use-case is pretty obvious - we have to define a LOV for the currency attribute and set autoSubmit=true for the inputText.




<af:inputText value="#{bindings.Ccy.inputValue}"
              label="#{bindings.Ccy.hints.label}"
              required="#{bindings.Ccy.hints.mandatory}"
              columns="#{bindings.Ccy.hints.displayWidth}"
              maximumLength="#{bindings.Ccy.hints.precision}"
              shortDesc="#{bindings.Ccy.hints.tooltip}" id="it1"
              autoSubmit="true">
    <f:validator binding="#{bindings.Ccy.validator}"/>
</af:inputText>

<af:outputText value="#{bindings.CurrencyName.inputValue}" id="ot1"/>


Actually besides a symbolic code like USD and EUR a currency has a numeric code like 840 and 978. Sometimes a user prefers to input a symbolic code and sometimes a numeric one. Let's allow users to input whatever they want - either symbolic or numeric code, and we will take care of both and show correct currency description. We are going to add to the VO a transient updatable attribute with two LOVs. A user inputs a value of this attribute and depending on the value's type (string or numeric)  we'll use a corresponding LOV. The first LOV looks for the currency description by symbolic code and the second one by numeric code.

The LOV for the symbolic code (Ccy):



And the LOV for the numeric code (Nbuid):



In order to switch between LOVs we use additional transient attribute CcyLovSwitcher:



And the value of the CcyLovSwitcher is going to be some Groovy expression:


The expression adf.object.ccyLovName refers to the method of our custom ViewRowImpl  class:

public String getCcyLovName() {

  //Return a corresponding LOV's name
  //depending on the value's type - string or numeric

  String looukupValue = getCcyLookup();    
  if (isNumeric(looukupValue))
      return "LOV_CcyLookupByID";
  else
      return "LOV_CcyLookupByCcy";

}



//Check whether the value is numeric
private boolean isNumeric(String value) {
  return value.matches("\\d+");
}


And now our users are free to input either symbolic or numeric currency code:


That's it!