Tuesday, May 17, 2011

Exposing List Model Instance as Table Component in Oracle ADF

Oracle ADF is based on the Model-View-Controller (MVC) design pattern. Separating applications into these layers simplifies maintenance and reuse of components across applications.
The Task

In this article, we will show how to create a table component (i.e., af:table) using a custom method in the Application Module. This custom method returns a List instance whose items will be displayed as rows in the table.

Here af:table exists in the View layer. Our custom method which provides table contents exists in the Business layer. This custom method is exposed on the client interface of an Application Module and exported to the Model layer as data control and binding object.

In this article, we will show how to achieve this. Before you start, read this companion article first.

The Background

The Table component uses a model to access the data in the underlying list. The specific model class is oracle.adf.view.rich.model.CollectionModel. You may also use other model instances, e.g., java.util.List, array, and javax.faces.model.DataModel. The Table will automatically convert the instance into a CollectionModel.

Normally, you would create table using ADF View Object which is exported to the Model layer. You create an ADF bound ADF Faces table by dragging a collection from the ADF Data Controls panel to a page or page fragment. Using this approach, you can get access to its CollectionModel easily. However, for some cases, creating a View Object can be difficult. For example, our query using Oracle Semantic Technology doesn't allow us to use bind variable.
select  postab.post_id, postab.content_value
from table(sem_match('(?s <http://xmlns.oracle.com/rdfctx/property/hasXxxOf>   <http://xmlns.oracle.com/rdfctx/organization/oracle>)',
  sem_models('smm_vpd_model_1','smm_ontology_model'),
  sem_rulebases('owlprime'), null, null))  graph,
  smm.smm_vpd_rdf_data_1 app,
  smm.smm_posts postab
where app.triple.rdf_p_id=oracle_orardf_res2vid('http://xmlns.oracle.com/rdfctx/property/hasXxxOf')
  and app.triple.rdf_c_id=oracle_orardf_res2vid('http://xmlns.oracle.com/rdfctx/organization/oracle')
  and graph.s$RDFVID=app.triple.rdf_s_id
  and postab.post_id=app.post_id
;
Ideally, we would like to make object in the RDF triple a bind variable. Since sem_match is an operator and doesn't allow that. The workaround will be creating a custom method which returns a List instance. Within the method, we create a custom query statement based on the name of an object (i.e., "oracle"). The signature of the custom method will be:
public List<postobj> positivePostsByBrand(String brandName) {
}
The Steps

Because of dependencies, we will work from Business Layer back to View Layer. All Fusion Web Applications are created with two projects:
  • Model
  • ViewController
For step 1 below, we will work in the Model project. For other steps, we will work in the ViewController project.

Step 1: Add Custom Method to the AM

Add our custom method (i.e., positivePostsByBrand) to the Application Module:
Shuttle custom method from the available list to the selected list:
In the Data Controls panel, you should find the new custom method listed under your application module.

Step 2: Create MethodAction Binding Object

Select the JSF page to which you want to add the new table. In its Bindings view, create a new binding object using the new data control object (i.e. our custom method positivePostsByBrand). By default, the binding objects are named after the data control object that was used to create it.
Our new binding object will be methodAction (under Generic Bindings) item type:

On Create Action Binding, select your AM (i.e., SemAppModuleDataControl). No, don't expand its contents. Look underneath for an Operation drop-down list. Select your custom method. Take a note of the name of its parameter (i.e., brandName) which you need to use later.
LinkStep 3: Create a New Getter Method on a Managed Bean

We need a getter method on a managed bean, which will fetch table contents on behave of View layer. For this exercise, we have created a managed bean named ActionBean which it has been registered on the ADF's unbounded task flow (i.e., adfc-conf.xml):

This managed bean has a session scope. Our getter method in ActionBean.java looks like this:
public List<PostObj> getPositivesByBrand() {
  if (getBrandName() == null || getBrandName().length() == 0)
    return new ArrayList<PostObj>();
  else {
    OperationBinding oper = getOperBindings("positivePostsByBrand")  ;
    Map params = oper.getParamsMap();
    params.put("brandName", brandName);
    oper.execute();
    return (List<PostObj>)oper.getResult();
  }
}
Within it, it uses a binding object named positivePostsByBrand to fetch table contents from the business layer (i.e., via our custom method defined in the Application Module implementation file). To execute our custom method, we also need a parameter named brandName. We will discuss how to provide this parameter at runtime later.

Step 4: Create New Table on JSF Page

Select the component (i.e., af:panelHeader) that you want to insert the new table to and insert a new ADF Faces component (i.e., af:table):
On Insert ADF Faces Item, select Table:
On Create ADF Faces Table, check Bind Data Now and click Browse...:
You will find our variable named positivesByBrand (i.e., exposed by the getter method) under ADF Managed Bean > actionBean. Select it and an EL will be created automatically for you.
Next we will create new columns mapped to the properties of the List instance object (i.e., PostObj). Click New:
Add new columns as shown. Their values will be mapped to the property's on our List instance object (i.e.,PostObj).
The Missing Link

As noted above, to execute the custom method we need a parameter (i.e., brandName). To provide that, we need to dynamically select that name from the UI. For example, in a master table, you may display rows with different brand name on each row. The brand name (or the count in our case) can then be implemented as an af:commandLink:

<af:commandLink  id="cl1" actionListener="#{actionsBean.positivePostsByBrand}">
  <af:outputText value="#{row.count}" id="ot5"/>
  <f:attribute name="posBrandName" value="#{row.brand}"/>
</af:commandLink>

Upon clicking, it will dispatch an ActionEvent which will be consumed by a listener (i.e., positivePostsByBrand method on our managed bean). Within the method, it will retrieve an attribute named posBrandName from the event's source object as such:
public void positivePostsByBrand( ActionEvent actionEvent){
  RichCommandLink _table = (RichCommandLink) actionEvent.getSource();
  String brandName = (String) _table.getAttributes().get("posBrandName");
  // set brand name to be used in getPositivesByBrand method
  setBrandName(brandName);
  getPositivesByBrand();
}

Note that attribute posBrandName will be set to be:
#{row.brand}
as defined in the af:commandLink.

References
  1. ADF Faces Rich Client
  2. Implement Contextual Event
  3. How-to Build a Generic Selection Listener for ADF bound Tables
  4. Invoking Application Module Custom Methods from Oracle ADF Frontend—Why and How?
  5. Action Bindings and Method Bindings in Oracle ADF 11g

No comments: