Skip to main content

Building programmatically managed business components - Part 1

I've noticed quite a lot of queries in the public forum on building programmatic entity object and view objects . Let me share an example on this subject which may help you in implementing such use cases quickly. You can see a sample from Steve as well on this topic - 132.Programmatic View Objects Showing Master/Detail Data. Even my example also follows the same approach, of course with some minor tweaks hare and there :-)

Why do you need programmatic entity and view Objects?

Consider a scenario where you may need to retrieve data from a custom data source (say using third party API for example) and may need to perform some data update later on. While realizing such use cases, you my still want to leverage the declarative development support provided by the JDeveloper for building list of values, entity caching and validation, declarative transaction support etc. Though ADF got binding support for Java services, its not as rich as ADF BC implementation. One possible option is to customize the business components (view object and entity object) to interact with third party data source by overriding its default life cycle methods. Before get in to the implementation, let me take a step back take you through life cycle of entity and view objects.

How does your view object work?

When you run a page with a table, during the 'render response' phase of the view, table may tries to get data  by evaluating the EL associated with value attribute. At this stage the binding layer will identify the underlying view object for the iterator which is bound to the table, and may retrieve the data by executing the view object. The view object execution takes the following steps

Step 1: The view object executes the query by calling executeQueryForCollection(...) and the associate the result with QueryCollection object.
ViewObjectImpl::executeQueryForCollection(java.lang.Object qc,
java.lang.Object[] params,
int noUserParams)


Step 2: Once the query is executed, view object starts the iteration over the result set(collection) obtained at 'Step 1' by calling hasNextForCollection(...) for each element in the collection
ViewObjectImpl::hasNextForCollection(java.lang.Object qc)

Step 3: If step2 (hasNextForCollection) return true, then call createRowFromResultSet(...) to convert the record in to ADF aware form. If the view object is based on entity object, then corresponding entity instance(s) is getting created at this stage. In this case the ViewRowImpl will act as a wrapper over the underlying entity instance. It means that if you do update/delete on ViewRowImpl, that may finally reach underlying entity instance and entity may get added to the transaction listener's dirty list.
ViewObjectImpl::createRowFromResultSet(java.lang.Object iqc,
java.sql.ResultSet resultSet)


Step 4: Repeat Step 2 and 3 for all elements in the result set(till hasNextForCollection returns false). At the end of iteration run time marks 'fetch complete' by calling setFetchCompleteForCollection(...)
ViewObjectImpl::setFetchCompleteForCollection(java.lang.Object qc,
boolean val)


Who is interested in ViewObjectImpl::getQueryHitCount(...)?

Well, some UI components like table, tree etc. which needs paginated data fetch + scrollable behavior may require this information. These folks will call count query first and then may go for view object execution.

Note: If you are building programmatic view object, without any entity back up, its really required to implement ViewObjectImpl::retrieveByKey(ViewRowSetImpl rs,Key key,int maxNumOfRows) to return row for the key from your custom data source. why? Well, when run time fails to find the row in the cache by calling findByKey(), as a next step it will try to invoke retrieveByKey() to retrieve the value by passing Key. Please see 42.1.8 Handling View Object Queries with Primary Keys Defined by Transient Attributes to learn more.

What does happen when you update some data and call commit ?

When you update some row from an entity based view object, actual update happens against the corresponding entity instance(see Step 3 in the above section). Later when you commit transaction, entity  posts changes to database and commit transaction. Let us take look at the steps of the transaction commit cycle for an entity object.

Step 1: While you call commit, transaction manager starts the post cycle for the dirty entities. The first step is to validate the business data by calling validateEntity().
EntityImpl::validateEntity()

Step 2: Then transaction manager calls postChanges() on each dirty row(entity instance). The post changes takes multiple sub tasks to finish the job as copied below.

EntityImpl::postChanges(...) : Initiates a post operation for this Entity Object.
    a) EntityImpl::lock() : Locks the row.
    b) EntityImpl::prepareForDML(...) : A pre-notification for preparing rows for posting to DB.
        If you need to update some dependency columns or custom history columns then this is
        the place to add that logic.
    c) EntityImpl::doDML(...) : Performs INSERT/UPDATE/DELETE processing for the row.

Step 3: Once the data is posted, next step is committing the transaction. The following methods gets executed before and after of transaction commit.

    a) EntityImpl::beforeCommit(...) : Reports that a commit operation has initiated
    b) EntityImpl::afterCommit(...) : Reports that a successful commit operation has occurred

Well, enough theory, time for action ! Let us go ahead and build programmatic entities and view objects. Though this is simple, you may need to pay attention to a couple of  'silly' points to make it working.
Read more...


Comments

Mukesh said…
Thanks for the info Jobinesh..
I did a sample app on this with a table and found that getQueryHitCount is resulting 354 but the createRowFromResultSet() is getting executed less number of times(50).It is not getting executed after that though the method hasNextForCollection() is returning true. Also setFetchCompleteForCollection() is not executed.
Mukesh said…
Also there is no pagination. I am displaying the entire data in the table
Jobinesh said…
Mukesh,
Can you help me to reproduce this in the sample I uploaded? Will take a look in that case.
mukesh said…
This comment has been removed by the author.
mukesh said…
In your sample application the method setFetchCompleteForCollection() is not explicitly overridden. You check the collection hasNextForCollection() and if it returns false you call the method setFetchCompleteForCollection() and set true. My new sample just displays the data in a table. All the methods that i override gets executed except for that setFetchCompleteforCollection().

More questions:
1.What is the difference between getQueryHitCount(ViewRowSetImpl)and getQueryHitCount(ViewRowSetImpl,Rows[]), though the later is automatically called once the former is called?

2.I combined this post with your another post.. The method prepareRowSetForQuery() is executed two times between getQueryHitCount(ViewRowSetImpl,Rows[]) and executeQueryForCollection(). Is there any reason for prepareRowSetForQuery() to get executed after getQueryHitCount()because the call before executeQueryForCollection() is understandable?

3. What is the difference between prepareRowSetForQuery() and prepareVOForQuery()?

I donot do much in these methods, just to get info about the commonly used methods.
Jobinesh said…
1&2.
getQueryHitCount(ViewRowSetImpl,Rows[]) is used if the child VO is executed as a result of a view link execution (depends on whether UI needs this count info)
3.
prepareRowSetForQuery is applicable for all rowsets(e.g Rowsets used by LOV/Tree etc)
mukesh said…
Jobinesh.. Could you please tell me where getCappedQueryHitCount() and getCappedRowCount() fit nto the VO lifecycle?
Amaan Junaid said…
This comment has been removed by the author.

Popular posts from this blog

How to set Bind Variable Values at runtime ?

In this post I'm sharing a couple of approaches for programmatically setting bind variables values at run time. This post is an attempt to explain 'When to use what ?'[ In case if you are familiar with 'Bind Variables' in ADF BC, please refer Section 5.10, Working with Bind Variables in Fusion Developer's Guide ]

1. Set the Bind Variable value using RowSet::setNamedWhereClauseParam(...)

You can use use the setNamedWhereClauseParam(...) method on the ViewObject interface (which extends oracle.jbo.RowSet) to set the value for bind variables. Please note this sets the value on default RowSet. In other words, this doesn't have any effect on the secondary RowSets that you/system generates.
ViewObject vo = am.findViewObject("EmployeesView1"); vo.setNamedWhereClauseParam("bindVarDeptId", new Number(10)); vo.executeQuery();
2. Set the Bind Variable value using ViewObject's VariableValueManager::setVariableValue(...)

VariableValueManager Ma…