Building an ADF Faces Tree Component with mixed (DEPT and EMP) nodes in 5 minutes using JHeadstart 10.1.3

1

For a project we are currently executing at AMIS, using the Oracle ADF, with ADF BC, Binding and Faces as well as JHeadstart 10.1.3 (currently in beta), the need arose to make use of Tree components in the web UI. A requirement for this tree was that it had to be mixed: nodes based on various ViewObjects. In this article, I will show how we were able to create such a tree in less than 5 minutes, using JHeadstart. We will also see what remaining issues there – apparently issues in ADF itself. 

An introduction to the ADF Faces Tree Component can be read in this previous blog article: Getting started with ADF Faces in JDeveloper 10.1.3 – Using the Tree Component, comparison with MyFaces Tomahawk Tree2

....
 

The first steps for getting this application up and running are fairly trivial:

Preparing the Application and Model project 

The first step is normal JHeadstart procedure. Our starting point is
the SCOTT schema (EMP and DEPT tables) and a JDeveloper 10.1.3 with
JHeadstart 10.1.3 (build 78) installation.

Create the Model Project 

  1. Start JDeveloper
  2. Create
    a new Application; choose the Web Application [JSF, ADF BC] Application
    Template. Call the Application for example HRM. JDeveloper creates two
    projects: Model and ViewController.
  3. On the Model project,
    select from the New Gallery the option Business Tier, ADF Business
    Components, Business Components for Tables
  4. Select or Create a Connection to the SCOTT schema
  5. Select
    the tables EMP and DEPT to create Entity and Updateable ViewObjects
    for; accept the default package and application module names
  6. Define
    primary keys for the EMP and DEPT EntityObjects: Select the Emp
    EntityObject; select the EMPNO attribute, check the Primary Key
    Checkbox, select the RowID attribute and uncheck the Primary Key
    checkbox. Select the DEPT EntityObject, select the EMPNO attribute,
    check the Primary Key Checkbox, select the RowID attribute and uncheck
    the Primary Key checkbox.
  7. Remove the RowID attribute from both ViewObjects
  8. Create a ViewLink from DeptView (source) to EmpView (target). The join condition links DeptView.Deptno to EmpView.Deptno
  9. Create a ViewLink from EmpView (source) to EmpView (target). The join condition links EmpView.Empno to EmpView.Mgr
  10. Add EmpView as child in the Application Module’s DataModel:

This
concludes the Model project. If you feel like testing it, you could run
the Test option from the Right Mouse Button Menu on the AppModule
Application Module.

Create and Generate the ViewController Project

Now we will generate a straightforward EMP and DEPT application

  1. From the RMB menu on the ViewController project, select the option Enable JHeadstart on this Project. Run and Finish the wizard.
  2. Again,
    from the RMB menu on the ViewController project, select the option New
    JHeadstart Application Definition. A wizard is run. Accept all defaults
    and Finish. A Default JHeadstart Application Definition file is
    created. Save All.
  3. You will find the new
    ApplicationDefinition file can be found under the node Resources in the
    ViewController project. Select the option Run JHeadstart Application
    Generator from the RMB menu on this file. The default application is
    now generated. Press Save All.
  4. Run the file EmpTable.jspx under ViewController\WebContent\WEB-INF\page to inspect the application.

Introduce the Tree Group to the Application

1. Open the JHeadstart Application Definition Editor

2. Create a New Group; call it HrmTree for example. Set the Layout-Style to Tree-Form. Select DeptView1 as DataCollection and as TreeDataCollection. Synchronize the Group to create Items for all Attributes. Set the Descriptor Item to Dname. Note: we have an opportunity here to distinguish between the DataCollection (ADF DataControl Iterator) to use for presenting the nodes in the tree and for creating the Edit Form for the selected node. We will not go into more details here.

 

 3. Create a Detail Group under the HrmTree group for the Detail (Emp) Nodes. Set the Tree Data Collection – the ADF DataControl iterator for the detail Emp nodes under parent Dept and (manager) Emp nodes – to EmpView2 (referring to the detail ViewObject Usages in the AppModule’s DataModel). Set the Data Collection to EmpView1. Specify the tree-form layout style. Synchronize the Group and set the Descriptor Item to Ename.

 

4. That is it! Now generate the application using the JHeadstart Application Generator which will create the PageDefinitions for the ADF Binding Framework, the JSF JSP page and supporting elements like faces-config.xml and a resource bundle.

5. When generation is done, run the application:

 

If we open one of the Department Nodes and select a Child Employee node the page looks like:

 

Remaining Issues

One very annoying issue with the Tree is that regardless of whether a node has children, it will be displayed as an exandable node (in fact it stays this way even when expansion has revealed that there are no leaf-nodes under this node). See for example SMITH in the screenshot above. I had noticed this before in my previous analysis of the ADF Faces Tree Component and consultation with Steven Davelaar  (Mr. JHeadstart) confirms that this is in fact a bug in ADF (apparently originally an intentional feature as inspecting all nodes for the existence of child nodes was deemed to performance expensive). Steven says it has now been recognized as a bug, and since it is hugely bothering him at his current project he is trying to put some pressure on the development team to have something done about it. It seems that the issue is not in the Tree Component itself, but in the ADF Binding Framework’s implementation of the TreeModel.

(modified 30th July 2006): A little digging reveals some more details:

The classes relevant to the way ADF (Binding) provides the TreeModel required by the ADF Faces Tree component are oracle.adfinternal.view.faces.model.binding.FacesCtrlHierBinding and its inner class FacesModel. The method isContainer that is invoked by the ADF Faces Tree renderer to determine which icon to display for a node is implemented as follows in this FacesModel class:

 

    public boolean isContainer()<br />    {<br />      // the following is too expensive at the moment: bug 4597193<br />//      JUCtrlHierNodeBinding node = _getRowData();<br />//      return node.getChildren() != null;<br />      return true;<br />    }<br /> <br />

Now my question would be: where is the configuration detail that links the FacesCtrlHierBinding class to the value="#{bindings.HrmTreeTree.treeModel}" attribute on the <af:tree > component? If I know how to change that configuration setting, I should be able to use my own subclass that extends FacesCtrlHierBinding and does implement the isContainer() method, at least whenever I want it to. I will try to find the answer to this question.

OK, some more research revealed that class oracle.adfinternal.view.faces.model.binding.FacesBindingDefFactoryImpl is the one responsible for tying the <tree> component in the PageDefinition to the FacesCtrlHierDef() class:

public final class FacesBindingDefFactoryImpl extends JUBindingDefFactoryImpl<br />{<br />   public DCDefBase createDefinition(DefElement element)<br />   {<br />     String sElement = element.getElementName();<br />     ...<br />     else if(sElement.equals(JUTags.PNAME_tree))<br />     {<br />       return new FacesCtrlHierDef();<br />     }<br />&nbsp;

Instead of a configurable mapping, it turns out to be a hard-coded reference to the FacesCtrlHierDef() class which in turns returns FacesCtrlHierBinding() instances. So the question now becomes: is the FacesBindingDefFactoryImpl class configured somewhere and if so, can it be replaced with my own Factory Class that can render different mappings for specific tags from the PageDefinition file? The next stage takes me to the oracle.jbo.uicli.mom.JUMetaObjectManager class. This class has the method getFactoryMap() that returns a Map that contains the name of the BindingDefFactory implementation class. This method will initiate the Factories oracle.jbo.uicli.binding.JUBindingDefFactoryImpl and DataControlDefinitionFactory if it cannot find the factory-definitions where it expects to find them. In my case it finds some settings using the call

  com.sun.java.util.collections.HashMap factoryMap =<br />    (com.sun.java.util.collections.HashMap)findLoadedObject(&quot;ADFNameSpaceMap&quot;); <br />

I have not been able to figure out so far where the values loaded by this method come from:

findLoadedObject is a method on the superclass oracle.jbo.mom.DefinitionManager. This class is located in the JDEV_HOME\BC4J\lib\adfm.jar file. I suppose this class is instantiated very early on in the application life cycle. However, where it gets its values from still eludes me. 

Thanks to Steve Muench who just pointed me at his Not Yet Documented Examples – I think I now know the way to go. See: Example of Using Customized ADF Binding Class in a ADF/Struts Web Application
In short:
MyADFBindingFilter extends the base ADFBindingFilter and calls
JUMetaObjectManager.setControlDefFactory() to register a custom
control binding definition
factory. MyCustomBindingDefFactoryImpl extends the default
JUBindingDefFactoryImpl and overrides the createControlDef()
method to return a different binding definition class for Trees.

Trying out Steve’s suggestion to configure the web.xml with my own ADFBindingFilter that sets my own ControlDefFactory, I perceive the following: during the very early stages of the application, a call is made from ADFPhaseListener to register the ControlDefFactory all by itself:

   /**<br />    * The definition factory used to create custom ADFm definition objects for<br />    * the Faces environment.<br />    */<br />   static<br />   {<br />      JUMetaObjectManager.getJUMom().registerDefinitionFactory(<br />         &quot;http://xmlns.oracle.com/adfm/uimodel&quot;,<br />         new oracle.adfinternal.view.faces.model.binding.FacesBindingDefFactoryImpl());<br />   }<br />

The first time the createDefinition() method on the JUBindingDefFactory is invoked, my own FactoryImpl is called. However, subsequent calls to oracle.jbo.uicli.mom.JUMetaObjectManager.getFactoryMap() seem to be returning the FacesBindingDefFactoryImpl() which in turn means my BindingDefFactory does not get to play anymore. Now when I add the same call made in this static block in ADFPhaseListener to my custom ADFBindingFilter, it seems like I get what I really want: a permanent replacement of the ControlDefFactory:

<br />public class MyADFBindingFilter extends ADFBindingFilter  {<br />    public void init(FilterConfig filterConfig) throws ServletException {<br />      super.init(filterConfig);<br />      JUMetaObjectManager.setControlDefFactory(new MyCustomBindingDefFactoryImpl());<br />        JUMetaObjectManager.getJUMom().registerDefinitionFactory(<br />                 &quot;http://xmlns.oracle.com/adfm/uimodel&quot;,new MyCustomBindingDefFactoryImpl());<br />}<br /><br />

The next step obviously is to implement my own extension to FacesCtrlHierBinding, one that works with my extension to the FacesModel class and that has a method isContainer with a proper implementation. More on this later.

Conclusion

Developing a fairly complex mixed node tree component with Node specific Edit Form is extremely simple with ADF Faces, ADF Business Components and JHeadstart 10.1.3. It is a construction that will feature quite prominently in the application we are currently developing, so the productivity gains we are getting are very substantial indeed.

Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

1 Comment

  1. Added some more analysis results on how the ADF Model/Binding stuff implements the TreeModel required by the ADF Tree component – and how it does not implement the isContainer() method.