Oracle ADF Use-Case at Tier One Bank

Filtering Values using Oracle ADF Select One Choice Component

I wanted to share with you a challenging Oracle ADF use-case I was working on recently, during an Oracle BPM project at an Australian Tier One Bank. The use-case related to using the Oracle ADF Select One Choice component to enforce uniqueness in the selected values - af:selectOneChoice in an ADF Table - af:table.

This is a fairly common requirement in many apps, however, it is not supported declaratively, out-of-the-box by ADF. This blog post gives you everything you need to solve this real world use-case so you can modify the standard behavior of the Oracle ADF Select One Choice component to ensure uniqueness of the list values selected.

NOTE : This sample Oracle ADF application can be downloaded from the Prerequisites section below.

Standard behaviour of af:selectOneChoice component

It is well known that as part of the standard behaviour of the Oracle ADF framework, when a select one choice component is added into a column of an ADF table and new rows of the table are created, the framework renders the select one choice as part of each and every new row, with all the values of the select one choice listed consistently.

This default behaviour is shown in the two snapshots below, where the new row created could be the first or any subsequent row.

Thereafter, no matter how many rows are created, the select one choice for each row still shows all the values, thereby allowing duplicate rows.

So what are we trying to solve ?

A slight variation to this standard behaviour is a fairly common use-case which requires that each row can only be selected once, ensuring uniqueness of each row selected. Specifically this means whenever a new row is added into the ADF table, the select one choice component in this new row filters out any list values that have already been selected in previous rows, thereby enforcing uniqueness.

Is this provided out-of-the-box ?

There are a lot of business use-cases that require this behaviour to ensure uniqueness of the data selected. Unfortunately, Oracle ADF does not provide an out-of-the-box declarative way to implement this. The purpose of this blog post is to provide a tested and well documented approach, so you can easily implement this functionality using ADF, for your own use-cases.

Some real-world examples
  1. Assigning employees into certain groups for an event where no employee can be part of multiple groups at the same time.
  2. Mapping sales representatives with distinct regions so that there is no overlapping of two regions with a sales person.
  3. Tracking milestones of an Employee in the company as part of a career path where once a milestone is completed, it cannot be repeated.
  4. Tracking progress using a well defined checklist.

Let's understand more of this behaviour using a case study. The sections below walk through a real world problem as part of the case study and guides you through the implementation to achieve the desired behaviour.

Case Study

Note: This sample case study has been developed to highlight the solution approach - this is not the actual scenario implemented during the project.

A manager would like to rank employees of his team based on their performance. In this particular case, the constraint is that no two employees shall have the same rank. In the user interface, the manager can select a rank and employee name using the select one choice component. To add or delete multiple employees from the ADF table, the add and delete functionality needs to be built on top of the ADF table.

The sections below walk through this case study and provide all the necessary steps and code snippets to implement this yourself.

Prerequisites

  • Download the the application EmployeeRanking to have a better understanding of the model and view controller artefacts described below in design time and runtime.
  • Download and install Oracle JDeveloper 12.2.1.2 as the application is developed using this version of the Oracle ADF framework. Open the downloaded project in JDeveloper.
  • Run the database script EmployeeRanking.sql which is part of the application downloaded in the data base.
  • Modify the Model Database properties to ensure the application points to the database where the script is run.

Database

This script EmployeeRanking.sql creates 3 tables.

  • EMPLOYEE_CATALOG : This table stores a catalog of employees that is shown as a list.
  • RANK_CATALOG : This table stores a catalog of ranks that is shown as another list.
  • EMPLOYEE_RANK : This table stores a mapping of an employee and his/her rank assigned by the manager.

Application Content

Entity objects EmployeeCatalogEO, EmployeeRankEO and RankCatalogEO are created for each of the database tables. Corresponding view objects EmployeeCatalogVO, EmployeeRankVO and RankCatalogVO are also created. The view objects are exposed in the EmployeeRankAM application module.

The EmployeeCatalogVO is used as a view accessor to the EmployeeId attribute of EmployeeRankVO. Similarly, the RankCatalogVO is used as a view accessor to the RankId attribute of EmployeeRankVO.

EmployeeRanking.jspx is the only page used as part of this demo. An ADF table is created as part of this page to manage the rows of EmployeeRankVO. To see the behaviour of the application mentioned in the Case Study section, run this page in JDeveloper.

Implementation

The following sections describe the code changes to get the desired filtered list behaviour, which is a deviation from the standard ADF framework behaviour. The modification involves changing the view accessor getter method in the view row implementation file, which is responsible for fetching the list values for the select one choice component.

In this case, the modified code is part of the two accessor methods in the EmployeeRankVORowImpl.java class. The accessor methods are
getEmployeeCatalogVO method getRankCatalogVO method.

As the modification to the getEmployeeCatalogVO method is going to be very similar to the getRankCatalogVO method, the following sections describe the changes to the getRankCatalogVO method and later these can be easily replicated to the getEmployeeCatalogVO method.

Returning the default rowset

The framework returns the default rowset as part of the getRankCatalogVO. If the user has already selected a value for the select one choice in the UI then it does not matter even if we return the default rowset for that select one choice. The rowset needs to be controlled only when the user creates a new row and has not yet selected a value for the select one choice. This is supported by adding the following null check.

if(this.getRankId() != null){ return rowset; }

Creating a View Criteria to filter the values

As values need to be filtered from the select one choice component, a view criteria is needed. Also, we would set the criteria mode to CRITERIA_MODE_CACHE because this is a classic case of in-memory filtering as the user would need to see this behaviour dynamically without committing the data.
ViewCriteria vc = rowSet.getViewObject().createViewCriteria(); vc.setCriteriaMode(ViewCriteria.CRITERIA_MODE_CACHE);

Evaluating the list of already selected values in the previous rows

The code snippet below is responsible for evaluating the already selected values of the select one choice component in the previous rows. This is the set of values that needs to be filtered for the select one choice, in the newly created row.

The following method is called to get the list of already selected ranks
ArrayList<String> ranks = getListOfExistingRanksAlreadySelected();

The method iterates over the existing rows and adds the selected ranks to a list and returns it to the caller method.

private ArrayList<String> getListOfExistingRanksAlreadySelected() { RowSetIterator ri = null; ArrayList<String> existingRanks = new ArrayList<String>(); try { //Create secondary row set to iterator over the employee-rank rows ri = (this.getEmployeeRankVO()).createRowSetIterator(null); while (ri.hasNext()) { EmployeeRankVORowImpl row = (EmployeeRankVORowImpl) ri.next(); if (row != null && row.getRankId() != null) { //This rank has been already been selected. Add to the list. existingRanks.add(row.getRankId()); } } } catch (Exception ex) { System.out.println("An exception occured"); } finally { if (ri != null) ri.closeRowSetIterator(); } return existingRanks; }

Add view criteria rows to filter already selected values

As the list of selected values has been retrieved, it is used in the view criteria to ensure the select one choice is filtered. A <> condition is applied for every value in the list as part of the view criteria row which is added to the view criteria.

//Build the view criteria by iterating through the already selected ranks Iterator iter = ranks.iterator(); while (iter.hasNext()) { String rank = (String)iter.next(); ViewCriteriaRow vcr1 = vc.createViewCriteriaRow(); vcr1.setAttribute("RankId", "<> " + rank); vcr1.setConjunction(ViewCriteriaRow.VC_CONJ_AND); vc.add(vcr1); }

Apply findByCriteria to get the new row set

The view criteria has been built and needs to be applied to the default row set that is part of the getter accessor method. A findByViewCriteria call is initiated with the query mode having a conjunction of ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES and
ViewObject.QUERY_MODE_SCAN_VIEW_ROWS to ensure that the criteria is applied on the rows both in the database and in the memory.

//Apply the view criteria with mode - SCAN VIEW ROWS | SCAN DATABASE TABLES ViewRowSetImpl rs = (ViewRowSetImpl)rowSet.findByViewCriteria(vc, -1, ViewObject.QUERY_MODE_SCAN_DATABASE_TABLES | ViewObject.QUERY_MODE_SCAN_VIEW_ROWS);

This is how the code would look for the getRankCatalogVO method.

Similarly, here are the getEmployeeCatalogVO method modifications.

Make the select one choice components read-only / disabled on selection

It is important to ensure that once a value has been selected from the select one choice components for either Rank or Employee, the components are immediately disabled or made read-only. This is because the ADF framework maintains multiple row sets of the select one choice component values for each and every row. Although we did filter the values of the select one choice for the new row, the values of the previous rows are still accessible to the user. This is a deviation from the problem that we are trying to solve.

Consider below once the Rank 1, Rank 2 and Rank 3 based rows are added and if the rows do not have the select one choice component disabled or read-only, then the user can select a duplicate value from the previous rows.

The select one choice components for both Rank and Employee have value change listeners defined in the EmployeeRankingBackingBean.java. Whenever a value from any of the select one choice component is selected, the component is made read-only.

The Add Rank button, which adds a new row to the table, has an action listener registered in the EmployeeRankingBackingBean.java. Whenever this button is clicked, the components are made editable again, so that values can be selected from them.

Testing

This section shows the behaviour of the select one choice components for Rank and Employee in different scenarios, after applying the discussed code changes.

Creating first Employee-Rank record

As no past records exist, the first record created will always show all the values in the select one choice component.

Rank - select one choice

Employee - select one choice

Creating multiple Employee-Rank records

After the first record is created, every record that is created thereafter will show the filtered values of the select one choices of both Rank and Employee, thus filtering the past records.

Rank - select one choice

Employee - select one choice

Deleting multiple Employee-Rank records

Multiple records have now been created in the Employee-Rank table from Rank 1 to Rank 5. To analyse the behaviour further Rank 2, Rank 3 and Rank 4 based rows are deleted. Only Rank 1 and Rank 5 records are shown in the table.

Creating an Employee-Rank record again

Now when a new record is added at this point, it can be seen that Rank 2, Rank 3 and Rank 4 records are included as well in the list. Same holds true for the employee list where John Smith, Harry Twan and Mark Robin are available to choose from.

Rank - select one choice

Employee - select one choice

Summary

In summary, often there is a requirement to manipulate select one choice and other list components from the default ADF behaviour, to show case lists dynamically based on business requirements. Such business logic can be easily integrated into the methods that are responsible for evaluating these lists, thus making Oracle ADF - a highly flexible framework.

This is a Model layer approach

The implementation described in this blog is purely a Model based approach. There is absolutely no change in the View or the Controller layer to achieve this behaviour.

Other View layer implementations

There are plenty of examples on the internet on how items in the select one choice components may be disabled. However, they do not explicitly talk about the use-case mentioned in this blog, and those implementations are done in the View layer. If you are looking to disable items in a select one choice component based on certain conditions then its worth reading them.

You might find these other blogs useful too:
Frank Nimphius' blog, Ashish Awasthi's blog, Susanto Paul's blog, Kunal Kumar's blog

Rubicon Red offers a set of innovative and market leading DevOps software, consulting and managed services solutions for Oracle middleware customers. Rubicon Red has deep experience in the fields of DevOps, Cloud Integration, SOA and BPM. Visit our website to learn more about our Services including Integration, Cloud Integration, BPM and Managed Services.

Contact us today - we would love to discuss your Oracle Cloud and Middleware projects and how we can help you deliver them.

Kapil Kapani

Kapil Kapani is a leading Oracle ADF and FMW Consultant at Rubicon Red with experience working in major Government and Financial institutions based in Australia and abroad.