Tree Demo

The ADF Faces tree is potentially a very rich component that can add value to many user interfaces. Data sets frequently are hierarchically organized and the tree allows visual representation of that structure. The user can quickly and intuitively navigate through the data to get to the element he or she has an interest in, select it and start doing work in the context of the selected tree node.

Using the ADF Faces Tree component together with the ADF Tree Data Binding – that allows configuration of the hierarchical data structure by nesting various ViewObjects – is pretty straightforward. However, we have run into at times quite severe performance issues. It seems that the Tree Data Binding executes a query for every node, to collect the children of that node. That means that even for a relatively small tree, as soon as we start expanding nodes, the number of queries executed increases rapidly.

We want to leverage the knowledge we have of our data model as well as the strong hierarchical query functions in the Oracle RDBMS, rather than having the ADF middle tier traverse the tree, executing queries with every node it encounters. As a bonus we also fix another issue with the Tree Data Binding: it does not indicate that a node is a leaf-node – i.e. has no children. All nodes are presented as containers (that is: with a plus sign suggesting the node has children). While that limitation is a a way to save on performance, it can be quite annoying to the end user.

In this article I will describe how we can easily populate the Tree Component from our own middle tier POJO that in turn is based on a Read Only ViewObject that uses a Hierarchical SQL query to retrieve the tree data from the database in a single round trip.
The example as always is a simple one: Employees in Departments. At the end of the article, the JDeveloper project is available for download.

The data structure in the database is probably familiar: my root-nodes are the records in the DEPT table. The highest level Employee Nodes are EMP records with JOB equals MANAGER. The lower level Employees connect to their parents via the Foreign Key reference from their MGR column to the EMPNO column in the same EMP table.

It is fairly easy to come up with a single SQL Query that returns the entire tree-structure in one go:

with nodes as
( select deptno id
, 'DEPT' node_type
, -1 parent_id
, dname node_label
from dept
union all
select empno id
, 'EMP' node_type
, deptno parent_id
, ename node_label
from emp
where job ='MANAGER'
union all
select empno id
, 'EMP' node_type
, mgr parent_id
, ename node_label
from emp
where job <>'MANAGER'
)
select node_label
, level
, id
, parent_id
, node_type
from nodes
connect
by prior id = parent_id
start with parent_id =-1
After creating a new JDeveloper Application – ADF BC and JSF Technology Template – I create a read only ViewObject HrmTreeView, based on this query. I also create a new Application Module – HrmServiceAppModule – and add a ViewObject usage for HrmTreeView to the application module.

Next I create a new JSF page. I drag the HrmTreeView collection from the Data Control Palette to the JSF page and drop it as a read only table. And now is the time to create a TreeModel object, based on the HrmTreeView Collection.

In a previous article – Building ADF Faces Tree based on POJOs (without using the ADF Tree Data Binding) – I explained in detail how to base an ADF Faces Tree on a POJO that implements the TreeModel interface. We will now take it one step further, by populating that TreeModel not from static data defined in the Class definition, but from the Iterator for the HrmTreeView collection.

I have created a Java Class, HierarchicalQueryTreeModel, that takes the name of an ADF Iterator Binding and uses it to populate the TreeModel. This TreeModel is then injected into the TreeHandler class, that we have seen introduced in the previous article mentioned above. The faces-config definitions of the required beans are as follows:

<managed-bean>
<managed-bean-name>HrmTreeHandler</managed-bean-name>
<managed-bean-class>nl.amis.adffaces.tree.TreeHandler</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>treemodel</property-name>
<value>#{HrmTreeModel.treemodel}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>HrmTreeModel</managed-bean-name>
<managed-bean-class>nl.amis.adffaces.tree.HierarchicalQueryTreeModel</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>hierarchicalIteratorName</property-name>
<value>HrmTreeViewIterator</value>
</managed-property>
</managed-bean>
we see that the HrmTreeModel bean – that is the instantiation of the TreeModel based on an Iterator with a predefined structure, more on that later – gets injected into the TreeHandler. The TreeHandler provides the foundation for the Tree component, as can be seen here:

<af:tree value="#{HrmTreeHandler.treemodel}" var="node"
focusRowKey="#{HrmTreeHandler.focusRowKey}"
varStatus="nodeStatus" binding="#{HrmTreeHandler.jsfTree}">
The Tree Component has its value attribute bound to the TreeHandler. It is also bound itself to the tree property in the TreeHandler.

The HrmTreeViewIterator is the iterator that was created in the PageDefinition when I dragged the HrmTreeView collection as read only table to the JSF page. It is used in the HierarchicalQueryTreeModel class to get the data from the underlying ViewObject and database. This class is implemented like this:

package nl.amis.adffaces.tree;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import java.util.Map;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;

import javax.faces.el.ValueBinding;

import oracle.adf.model.binding.DCIteratorBinding;
import oracle.adf.view.faces.model.TreeModel;

import oracle.binding.BindingContainer;

import oracle.jbo.Row;
import oracle.jbo.RowSetIterator;
import oracle.jbo.domain.Number;

public class HierarchicalQueryTreeModel {

private String hierarchicalIteratorName;
private String rootLabel;
private TreeModel treemodel;

public HierarchicalQueryTreeModel() {
}

private TreeNode findParent(List<TreeNode> nodes, int level) {
// get last of the rootnodes
TreeNode tn = nodes.get(nodes.size() - 1);
// the parent for this node will be one level higher than the parameter level;
// so if level = 2, than the parent will be on level 1 (rootlevel); if the level is 3,
// the parent will be on level 2; the parent is the last node at the parentlevel
for (int i = level; i > 2; i--) {
// get lastchild of current node
tn = (TreeNode)tn.getChildren().toArray()[tn.getChildCount() - 1];
}
return tn;
}

private TreeModel initializeTreeModel() {
// create the list of root nodes:
List nodes = new ArrayList();
// TreeNode root = new TreeNode(getRootLabel(), "root");

DCIteratorBinding hierarchicalIterator =
(DCIteratorBinding)getBindings().get(hierarchicalIteratorName);
RowSetIterator hierarchicalRSI =
hierarchicalIterator.getRowSetIterator();
Row currentNode = hierarchicalRSI.first();
boolean firstNode = true;
do {
// if the level of a node is larger than the previous one, it is a child

if (!firstNode)
currentNode = hierarchicalRSI.next();
else
firstNode = false;

TreeNode treenode =
new TreeNode((String)currentNode.getAttribute("NodeLabel"),
(String)currentNode.getAttribute("NodeType"));
treenode.setLevel(((Number)currentNode.getAttribute("Level")).intValue());
Map attributes = new HashMap();
// copy all attributes from the Row to the Node
// note: the Row will only be available as long as we are on a page
// that has the iterator instantiated (which is basically only right at the
// time of instantiating the TreeModel
for (int i = 0; i < currentNode.getAttributeCount(); i++) {
attributes.put(currentNode.getAttributeNames()[i],
currentNode.getAttribute(i));
}
treenode.setAttributes(attributes);
if (treenode.getLevel() == 1) {
nodes.add(treenode);
} else {
findParent(nodes, treenode.getLevel())
.getChildren().add(treenode);
}
} while (hierarchicalRSI.hasNext()); // Master
return new SpecialTreeModel(nodes, "children");

}

public BindingContainer getBindings() {
FacesContext fc = FacesContext.getCurrentInstance();
Application app = fc.getApplication();
ValueBinding vb = app.createValueBinding("#{bindings}");
return (BindingContainer)vb.getValue(fc);
}

public void setHierarchicalIteratorName(String hierarchicalIteratorName) {
this.hierarchicalIteratorName = hierarchicalIteratorName;
}

public String getHierarchicalIteratorName() {
return hierarchicalIteratorName;
}

public void setRootLabel(String rootLabel) {
this.rootLabel = rootLabel;
}

public String getRootLabel() {
return rootLabel;
}

public TreeModel getTreemodel() {
if (treemodel == null) {
treemodel = initializeTreeModel();

}
return treemodel;
}
}
About the author : Lucas JellemaLucas 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, ADF, Java in various shapes and forms and many other things. Author of ...More by Lucas Jellema« Previous postNext post ».Much Faster ADF Faces Tree – using a POJO as cache based on Read Only View Object – (showing proper leaf nodes again)
The ADF Faces tree is potentially a very rich component that can add value to many user interfaces. Data sets frequently are hierarchically organized and the tree allows visual representation of that structure. The user can quickly and intuitively navigate through the data to get to the element he or she has an interest in, select it and start doing work in the context of the selected tree node.



Using the ADF Faces Tree component together with the ADF Tree Data Binding – that allows configuration of the hierarchical data structure by nesting various ViewObjects – is pretty straightforward. However, we have run into at times quite severe performance issues. It seems that the Tree Data Binding executes a query for every node, to collect the children of that node. That means that even for a relatively small tree, as soon as we start expanding nodes, the number of queries executed increases rapidly.

We want to leverage the knowledge we have of our data model as well as the strong hierarchical query functions in the Oracle RDBMS, rather than having the ADF middle tier traverse the tree, executing queries with every node it encounters. As a bonus we also fix another issue with the Tree Data Binding: it does not indicate that a node is a leaf-node – i.e. has no children. All nodes are presented as containers (that is: with a plus sign suggesting the node has children). While that limitation is a a way to save on performance, it can be quite annoying to the end user.

In this article I will describe how we can easily populate the Tree Component from our own middle tier POJO that in turn is based on a Read Only ViewObject that uses a Hierarchical SQL query to retrieve the tree data from the database in a single round trip.
The example as always is a simple one: Employees in Departments. At the end of the article, the JDeveloper project is available for download.

The data structure in the database is probably familiar: my root-nodes are the records in the DEPT table. The highest level Employee Nodes are EMP records with JOB equals MANAGER. The lower level Employees connect to their parents via the Foreign Key reference from their MGR column to the EMPNO column in the same EMP table.

It is fairly easy to come up with a single SQL Query that returns the entire tree-structure in one go:

with nodes as
( select deptno id
, 'DEPT' node_type
, -1 parent_id
, dname node_label
from dept
union all
select empno id
, 'EMP' node_type
, deptno parent_id
, ename node_label
from emp
where job ='MANAGER'
union all
select empno id
, 'EMP' node_type
, mgr parent_id
, ename node_label
from emp
where job <>'MANAGER'
)
select node_label
, level
, id
, parent_id
, node_type
from nodes
connect
by prior id = parent_id
start with parent_id =-1
The result of this query:



After creating a new JDeveloper Application – ADF BC and JSF Technology Template – I create a read only ViewObject HrmTreeView, based on this query. I also create a new Application Module – HrmServiceAppModule – and add a ViewObject usage for HrmTreeView to the application module.

Next I create a new JSF page. I drag the HrmTreeView collection from the Data Control Palette to the JSF page and drop it as a read only table. And now is the time to create a TreeModel object, based on the HrmTreeView Collection.

In a previous article – Building ADF Faces Tree based on POJOs (without using the ADF Tree Data Binding) – I explained in detail how to base an ADF Faces Tree on a POJO that implements the TreeModel interface. We will now take it one step further, by populating that TreeModel not from static data defined in the Class definition, but from the Iterator for the HrmTreeView collection.

I have created a Java Class, HierarchicalQueryTreeModel, that takes the name of an ADF Iterator Binding and uses it to populate the TreeModel. This TreeModel is then injected into the TreeHandler class, that we have seen introduced in the previous article mentioned above. The faces-config definitions of the required beans are as follows:

<managed-bean>
<managed-bean-name>HrmTreeHandler</managed-bean-name>
<managed-bean-class>nl.amis.adffaces.tree.TreeHandler</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>treemodel</property-name>
<value>#{HrmTreeModel.treemodel}</value>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>HrmTreeModel</managed-bean-name>
<managed-bean-class>nl.amis.adffaces.tree.HierarchicalQueryTreeModel</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
<managed-property>
<property-name>hierarchicalIteratorName</property-name>
<value>HrmTreeViewIterator</value>
</managed-property>
</managed-bean>
we see that the HrmTreeModel bean – that is the instantiation of the TreeModel based on an Iterator with a predefined structure, more on that later – gets injected into the TreeHandler. The TreeHandler provides the foundation for the Tree component, as can be seen here:

<af:tree value="#{HrmTreeHandler.treemodel}" var="node"
focusRowKey="#{HrmTreeHandler.focusRowKey}"
varStatus="nodeStatus" binding="#{HrmTreeHandler.jsfTree}">
The Tree Component has its value attribute bound to the TreeHandler. It is also bound itself to the tree property in the TreeHandler.

The HrmTreeViewIterator is the iterator that was created in the PageDefinition when I dragged the HrmTreeView collection as read only table to the JSF page. It is used in the HierarchicalQueryTreeModel class to get the data from the underlying ViewObject and database. This class is implemented like this:

package nl.amis.adffaces.tree;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import java.util.Map;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;

import javax.faces.el.ValueBinding;

import oracle.adf.model.binding.DCIteratorBinding;
import oracle.adf.view.faces.model.TreeModel;

import oracle.binding.BindingContainer;

import oracle.jbo.Row;
import oracle.jbo.RowSetIterator;
import oracle.jbo.domain.Number;

public class HierarchicalQueryTreeModel {

private String hierarchicalIteratorName;
private String rootLabel;
private TreeModel treemodel;

public HierarchicalQueryTreeModel() {
}

private TreeNode findParent(List<TreeNode> nodes, int level) {
// get last of the rootnodes
TreeNode tn = nodes.get(nodes.size() - 1);
// the parent for this node will be one level higher than the parameter level;
// so if level = 2, than the parent will be on level 1 (rootlevel); if the level is 3,
// the parent will be on level 2; the parent is the last node at the parentlevel
for (int i = level; i > 2; i--) {
// get lastchild of current node
tn = (TreeNode)tn.getChildren().toArray()[tn.getChildCount() - 1];
}
return tn;
}

private TreeModel initializeTreeModel() {
// create the list of root nodes:
List nodes = new ArrayList();
// TreeNode root = new TreeNode(getRootLabel(), "root");

DCIteratorBinding hierarchicalIterator =
(DCIteratorBinding)getBindings().get(hierarchicalIteratorName);
RowSetIterator hierarchicalRSI =
hierarchicalIterator.getRowSetIterator();
Row currentNode = hierarchicalRSI.first();
boolean firstNode = true;
do {
// if the level of a node is larger than the previous one, it is a child

if (!firstNode)
currentNode = hierarchicalRSI.next();
else
firstNode = false;

TreeNode treenode =
new TreeNode((String)currentNode.getAttribute("NodeLabel"),
(String)currentNode.getAttribute("NodeType"));
treenode.setLevel(((Number)currentNode.getAttribute("Level")).intValue());
Map attributes = new HashMap();
// copy all attributes from the Row to the Node
// note: the Row will only be available as long as we are on a page
// that has the iterator instantiated (which is basically only right at the
// time of instantiating the TreeModel
for (int i = 0; i < currentNode.getAttributeCount(); i++) {
attributes.put(currentNode.getAttributeNames()[i],
currentNode.getAttribute(i));
}
treenode.setAttributes(attributes);
if (treenode.getLevel() == 1) {
nodes.add(treenode);
} else {
findParent(nodes, treenode.getLevel())
.getChildren().add(treenode);
}
} while (hierarchicalRSI.hasNext()); // Master
return new SpecialTreeModel(nodes, "children");

}

public BindingContainer getBindings() {
FacesContext fc = FacesContext.getCurrentInstance();
Application app = fc.getApplication();
ValueBinding vb = app.createValueBinding("#{bindings}");
return (BindingContainer)vb.getValue(fc);
}

public void setHierarchicalIteratorName(String hierarchicalIteratorName) {
this.hierarchicalIteratorName = hierarchicalIteratorName;
}

public String getHierarchicalIteratorName() {
return hierarchicalIteratorName;
}

public void setRootLabel(String rootLabel) {
this.rootLabel = rootLabel;
}

public String getRootLabel() {
return rootLabel;
}

public TreeModel getTreemodel() {
if (treemodel == null) {
treemodel = initializeTreeModel();

}
return treemodel;
}
}
Now we have all the pieces, under the condition that the ViewObject publishes that least the following Attributes: Level, NodeLabel, NodeType. Note: all other attributes you may define on the ViewObject are available on the TreeNode object using node.attributes['nameOfAttribute'].



At the present, the TreeModel is instantiated only once and will be static from there on. It is quite simple to add little more logic to the TreeModel to allow the user to explicitly refresh from the database or to perform such a refresh under certain conditions. It will still a single roundtrip for the entire tree. Only for much larger trees (many thousands of nodes or more will this approach become inferior to the ADF Tree Data Binding).

Adding an icon for each type of node is easy – as per the suggestion by Steve Muench. The result looks like this:

<f:facet name="nodeStamp">
<af:panelGroup>
<af:objectImage source="#{node.nodeType=='DEPT'?'/dept.png':(node.nodeType=='EMP'?'/emp.png':'/manager.png')}"
shortDesc="#{node.nodeType}"/>
<af:commandLink text="#{node.description}">
<af:setActionListener from="#{ObjectTreeHandler.jsfTree.rowKey}"
to="#{ObjectTreeHandler.focusRowKey}"/>
<af:setActionListener from="#{node}"
to="#{ObjectTreeHandler.selectedNode}"/>
<af:resetActionListener/>
</af:commandLink>
</af:panelGroup>
</f:facet>
Well, I also changed the node type for the manager nodes from EMP to MANAGER.

Another Example
I can easily create another query, one that returns from a the current USER’s schema all objects with for Tables also the Column and Index details. The query looks like this:

with nodes as
( select distinct object_type id
, 'TYPE' node_type
, 'TYPE' parent_id
, object_type node_label
from user_objects
where object_type in ('TABLE','VIEW','PACKAGE','PROCEDURE','FUNCTION')
union all
select object_name id
, 'OBJECT' node_type
, object_type parent_id
, object_name node_label
from user_objects
where object_type in ('TABLE','VIEW','PACKAGE','PROCEDURE','FUNCTION')
union all
select table_name||'.'||column_name id
, 'COLUMN' node_type
, table_name parent_id
, column_name node_label
from user_tab_columns
union all
select INDex_name id
, 'INDEX' node_type
, table_name parent_id
, index_name||' '||index_type node_label
from user_indexes
)
select node_label
, level
, id
, parent_id
, node_type
from nodes
connect
by prior id = parent_id
start with parent_id = 'TYPE'
order siblings by node_label
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值