Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g Release 3 (10.1.3.0) Part Number B25947-02 |
|
|
View PDF |
Using the SRDemo application's SRService
as an example, this chapter describes how an application module's active data model and business service interface methods appear at design time for drag and drop data binding and how they are accessible at runtime by the ADF Model data binding layer using the application module data control.
This chapter includes the following sections:
Section 10.1, "Overview of Data Controls and Declarative Bindings"
Section 10.2, "Understanding the Application Module Data Control"
Section 10.3, "How an Application Module Appears in the Data Control Palette"
Section 10.5, "Application Module Databinding Tips and Techniques"
Section 10.6, "Overview of How SRDemo Pages Use the SRService"
The Oracle ADF Model layer is a declarative data binding facility. It implements the two concepts in the JSR-227 specification that enable decoupling the user interface technology from the business service implementation: data controls and declarative bindings. Data controls and declarative bindings enable a unified design time and runtime approach to bind any user interface to any backend business service without code.
Data controls abstract the implementation technology of a business service by using standard metadata interfaces to describe the service's operations and data collections. This includes information about the properties, methods, and types involved. At design time, visual tools like JDeveloper can leverage the standard service metadata to simplify binding UI components any data control operation or data collection. At runtime, the generic Oracle ADF Model layer reads the information describing your data controls and bindings from appropriate XML files and implements the two-way "wiring" that connects your user interface to your business service.
Declarative bindings abstract the details of accessing data from data collections in a data control and of invoking its operations. There are three basic kinds of declarative binding objects that automate the key aspects of data binding that all enterprise applications require:
Iterator bindings, which bind to an iterator that tracks the current row in a data collection
Value bindings, which connect UI components to attributes in a data collection
Action bindings, which invoke custom or built-it operations on a data control or its data collections
Iterator bindings simplify building user interfaces that allow scrolling and paging through collections of data and drilling-down from summary to detail information. UI components that display data use value bindings. Value bindings range from the most basic variety that works with a simple text field to more sophisticated list, table, and tree bindings that support the additional needs of list, table, and tree UI controls. An action binding is used by UI components like hyperlinks or buttons to invoke methods. An action binding allows the user to click on the component to invoke a business service without code. There are two kinds of action bindings: a regular action binding that invokes a built-in operation, and a method action binding that invokes a custom operation.
Note:
Value bindings are bindings that have a bound attribute value. All value bindings implement theoracle.binding.AttributeBinding
interface. The interface for action bindings is oracle.binding.OperationBinding
. Since both of these kinds of binding interfaces are related to UI controls, they both extend the oracle.binding.ControlBinding
interface. The term control bindings is used in this guide to describe things that are common to both value bindings and action bindings.The application module data control is one of the several data control implementations supplied with Oracle ADF. Its job is to be a thin adapter over an application module pool that automatically acquires an available application module instance at the beginning of the request. During the current request the application module data control holds a reference to the application module instance on behalf of the current user session. At the end of the request, the application module data control releases the application module instance back to the pool. Importantly, the application module component directly implements the interfaces that the binding objects expect for data collections, built-in operations, and service methods. This allows the bindings to work directly with the application modules and the view object instances in its active data model. Specifically, this optimized interaction allows:
Iterator bindings to directly bind to the default row set iterator of the default row set of any view object instance
Action bindings to directly bind to either:
Custom methods on the application module's client interface
Built-in operations of the application module and view objects
Figure 10-2 illustrates the pool management role the application module data control plays and highlights the direct link between the bindings and the application module instance.
At design time, you use the Data Control Palette to perform drag and drop data binding for JSF, JSP/Struts, and Swing applications. Each application module in the workspace appears automatically in the Data Control Palette. Using the SRService
application module from the SRDemo application as an example this section highlights exactly what you'll see in the Data Control Palette when you work with your own application modules.
Figure 10-3 shows the SRService
application module that implements the business service layer of the SRDemo application. Notice that its data model includes numerous view object instances, including several master/detail hierarchies. The view layer of the demo consists of JSF pages whose UI components are bound to data from the view object instances in the SRService
's active data model, and to built-in operations and service methods on its client interface. In Section 10.6, "Overview of How SRDemo Pages Use the SRService", you'll find an overview of exactly what aspects of this application module each page uses.
By default, an application module will appear in the Data Control Palette as a data control named AppModuleName
DataControl
. For example, the SRService
originally had the name SRServiceDataControl
. To change the default data control name to a shorter, or simply more preferable name, do the following:
To change the application module name:
Open the application module in the Application Module Editor.
Open the Custom Properties page.
In the Name combobox, select the DATA_CONTROL_NAME
property from the dropdown list.
Enter your preferred data control name in the Value field and click OK to close the wizard.
Figure 10-4 shows the custom property setting that changed the data control name of the SRService
from the default SRServiceDataControl
to the shorter SRService
name that matches the name of the application module. You'll notice the change immediately in the Data Control Palette.
Note that as you begin to bind data from the application module to your application pages or panels, the data control name for your application module will appear in the DataBindings.cpx
file in your user interface project and in each data binding page definition XML file. In addition, you might refer to the data control name in code when needing to work programmatically with the application module service interface. For this reason, if you plan to change the name of your application module, Oracle recommends doing this change before you being building your view layer.
Note:
In JDeveloper Release 3, if you decide to change the application module's data control name after you have already referenced it in one or more pages, you will need to open the page definition files where it is referenced and update the old name to the new name manually. Future releases of JDeveloper may extend its refactoring support to make renaming a data control simpler.Figure 10-5 illustrates how the Data Control Palette displays the view object instances in the SRService
's active data model. Each view object instance appears as a named data collection whose name matches the view object instance name. Note the hierarchical structure of the data collections, and that for viewing simplicity, the figure omits some details in the tree that appear for each view object. These additional view object details are highlighted in Section 10.3.6, "How View Objects Appear in the Data Control Palette". The Data Control Palette reflects the master/detail hierarchies in your application module data model by displaying detail data collections nested under their master data collection.
The Data Control Palette also displays each custom method on the application module's client interface as a named data control custom operation whose name matches the method name. If a method accepts arguments, they appear in a Parameters folder as operation parameters nested inside the operation node.
When you initially add a view object instance to the data model, if you haven't typed in an instance name yourself, it gets added with a name composed of the view object name with a numeric suffix appended. For example, adding an instance of a ServiceRequests
view object to the data model, the default view object instance name would be ServiceRequests1
. You can easily rename the view object instances in the Data Model page of the Application Module Editor.
As you begin to bind data from the data collections in the Data Control Palette to your application pages or panels, in addition to the data control name, the data collection names will be referenced in the page definition XML files used by the ADF Model data binding layer. Since the names of your view object instance in the application module data model are used as the names of these data collections, Oracle recommends reviewing your view object instance names before using them to build data bound pages to ensure the names are descriptive.
Note:
In JDeveloper Release 3, if you decide to change a view object instance name after you have already referenced it in one or more pages, you will need to open the page definition files where it is referenced and update the old name to the new name manually. Future releases of JDeveloper may extend its refactoring support to make renaming a view object instance simpler.The application module data control exposes two data control built-in operations named Commit
and Rollback
as shown in Figure 10-6. At runtime, when these operations are invoked by the data binding layer, they delegate to the commit()
and rollback()
methods of the Transaction
object associated with the current application module instance. Note that the Operations folder in the data controls tree omits all of the data collections and custom operations for a more streamlined view.
Note:
In an application module with many view object instances and custom methods, you may need to scroll the Data Control Palette display to find the Operations folder that is the direct child node of the data control. This folder is the one that contains its built-in operations.Figure 10-7 shows how each view object instance in the application module's data model appears in the Data Control Palette. The view object attributes are displayed as immediate child nodes of the corresponding data collection. If you have selected any custom methods to appear on the view object's client interface, like the performSearch()
method in the figure, they appear as custom operations immediately following the view object attribute at the same level. If the method accepts arguments, these appear in a nested Parameters folder as operation parameters.
As shown in Figure 10-7, the Operations folder under the data collection displays all its available built-in operations. If an operation accepts one or more parameters, then they appear in a nested Parameters folder. At runtime, when one of these data collection operations is invoked by name by the data binding layer, the application module data control delegates the call to an appropriate method on the ViewObject
interface to handle the built-in functionality. The built-in operations fall into three categories: operations that affect the current row, operations that refresh the data collection, and all other operations.
Create
— creates a new row that becomes the current row
Delete
— deletes the current row
First
— sets the current row to the first row in the row set
Last
— sets the current row to the last row in the row set
Previous
— sets the current row to the previous row in the row set
Next
— sets the row to the next row in the row set
Previous Set
— navigates forward one full page of rows
Next Set
— navigates backward one full page of rows
setCurrentRowWithKey
— tries to finds a row using the serialized string representation of row key passed as a parameter. If found, it becomes the current row.
setCurrentRowWithKeyValue
— tries to finds a row using the primary key attribute value passed as a parameter. If found, it becomes the current row.
Execute
— refreshes the data collection by (re)executing the view object's query, leaving any bind parameters at their current values.
ExecuteWithParams
— refreshes the data collection by first assigning new values to the named bind variables passed as parameters, then (re)executing the view object's query.
Note:
TheExecuteWithParams
operation only appears for view objects that have defined one or more named bind variables at design time.If you build composite application modules, by including nested instances of other application modules, the Data Control Palette reflects this component assembly in the tree hierarchy. For example, assume that in addition to the SRDemo application's oracle.srdemo.model.SRService
application module that you have also created the following application modules in the same package:
An application module named ProductService
, and renamed its data control to ProductService
An application module named CompositeService
, and renamed its data control to CompositeService
Then assume that you've added two view object instances named OtherViewObject
and AnotherViewObject
to the data model of CompositeService
and that on Application Modules page of the Application Module Editor you have added an instance of the SRService
application module and an instance of the ProductMaintenance
application module to reuse them as part of CompositeService
. Figure 10-8 illustrates how your CompositeService
would appear in the Data Control Palette. The nested instances of SRService and ProductService appear in the palette tree display nested inside of the CompositeService
data control. The entire data model and set of client methods that the nested application module instances expose to clients are automatically available as part of the CompositeService
that reuses them.
One possibly confusing point is that even though you have reused nested instances of SRService
and ProductService
inside of CompositeService
, the SRService
and ProductService
application modules also appear themselves as top-level data control nodes in the palette tree. JDeveloper assumes that you might want to sometimes use SRService
or ProductService
on their own as a separate data controls from CompositeService
, so it displays all three of them. You need to be careful to perform your drag and drop data binding from the correct data control. If you want your page to use a view object instance from the nested SRService
instance's data model that is an aggregated part of the CompositeService
data control, then ensure you select the data collection that appears as part of the CompositeService
data control node in the palette.
It is important to do the drag and drop operation that corresponds to your intended usage. When you drop a data collection from the top-level SRService
data control node in the palette, at runtime your page will use an instance of the SRService
application module acquired from a pool of SRService
components. When you drop a data collection from the nested instance of SRService
that is part of CompositeService
, at runtime your page will use an instance of the CompositeService
application module acquired from a pool of CompositeService
components. Since different types of application module data controls will have distinct transactions and database connections, inadvertently mixing and matching data collections from both a nested application module and a top-level data control will lead to unexpected runtime behavior. Forewarned is forearmed.
To add a Create button, drag and drop a Create
operation for a data collection from the Data Control Palette onto a JSP page or Swing panel.
Note:
The built-inCreate
operation behaves differently in a web-based application than in a Swing-based desktop application. For web applications, depending on the situation, you may want to use CreateInsert
instead of Create.If you drag a Create
operation for a data collection from the Data Control Palette onto a JSP page — whether using JSF or not — you'll get a Create button on your page that is declaratively bound to the built-in Create
operation. The Create
operation creates a new row for the data collection, but does not insert it into the data collection's row set. By not inserting the new row into the row set at creation time, it helps avoid having an unwanted blank row appear in other pages should the user navigate away from your create page before actually entering any data for the new row. After you've invoked a Create
operation, the iterator binding temporarily points to this new row as if it were the current row. When the user successfully submits data for the attributes in the new row, the new row gets inserted into the row set at that time. This is the declarative row creation approach that works best for most web application use cases.
If you drag a Create
operation for a data collection from the Data Control Palette onto a Swing panel, you'll get a Create button on your panel that is declaratively bound to a built-in operation called CreateInsert
instead. The CreateInsert
operation creates a new row in the data collection and inserts that new row into the collection just before the current row. CreateInsert is the best approach for Swing applications.
There are some situations in a web application where you need to use CreateInsert
instead of Create
. Use CreateInsert when creating:
An editable table control
A table with a single, current editable row
A Master/detail page and want the newly created master row to correctly show no existing detail rows
CreateInsert
is used when a new row needs to be inserted into the row set. Since only the one Create
operation shows in the Data Control Palette, to use a CreateInsert
operation in a web application involves a couple of additional steps.
How to Change a Create Operation to CreateInsert:
Drop the Create
operation from the Data Control Palette onto your page
Select the button in the visual editor and choose Edit Binding... from the context menu
In the Action Binding Editor use the Select an Action dropdown list to change the action binding from using the Create
to using CreateInsert
instead.
When you use the Create
or CreateInsert
operations to declaratively create a new row, under the covers they end up performing the following lines of code:
// create a new row for the view object Row newRow = yourViewObject.createRow(); // mark the row as being "initialized", but not yet new newRow.setNewRowState(Row.STATUS_INITIALIZED);
In addition, if you are using the CreateInsert
operation, it performs the additional line of code to insert the row into the row set:
// insert the new row into the view object's default rowset yourViewObject.insertRow(newRow);
When you create a row in an entity-based view object, the Transaction
object associated to the current application module immediately takes note of the fact. The new entity row that gets created behind the view row is already part of the Transaction
's list of pending changes. When a newly created row is marked as having the initialized state, it is removed from the Transaction
's pending changes list and is considered a blank row in which the end user has not yet entered any data values. The term initialized is appropriate since the end user will see the new row initialized with any default values that the underlying entity object has defined. If the user never enters any data into any attribute of that initialized row, then it is as if the row never existed. At transaction commit time, since that row is not part of the Transaction
's pending changes list, no INSERT
statement will be attempted for it.
As soon as at least one attribute in an initialized row is set, it automatically transitions from the initialized status to the new status (Row.STATUS_NEW
). At that time, the underlying entity row gets enrolled in the Transaction
's list of pending changes, and the new row will be permanently saved the next time you commit the transaction.
Note:
If the end user performs steps while using your application that result in creating many initialized rows but never populating them, it might seem like a recipe for a slow memory leak. Not to worry, however. The memory used by an initialized row that never transitions to the new state will eventually be reclaimed by the Java virtual machine's garbage collector.{para}?>
When building pages you'll often want to display some kind of record status indicator like: "Record 5 of 25". If you display multiple rows on a page, then you may also want to display a variant like "Record 5-10 of 25". You can build a record indicator like this using simple text components, each of which displays an appropriate value from an iterator binding or table binding using an EL expression. The iterator binding's rangeSize
property defines how many rows per page it makes available to display in the user interface. If your page definition contains either an iterator binding named SomeViewIter
or a table binding named SomeView
, you can reference the following EL expressions:
Number of Rows per Page
#{bindings.
SomeViewIter
.rangeSize}
#{bindings.
SomeView
.rangeSize}
Total Rows
#{bindings.
SomeViewIter
.estimatedRowCount}
#{bindings.
SomeView
.estimatedRowCount}
First Row on the Current Page
#{bindings.
SomeViewIter
.rangeStart + 1}
#{bindings.
SomeView
.rangeStart + 1}
Last Row on the Current Page
#{bindings.
SomeViewIter
.rangeStart + bindings.
SomeViewIter
.rangeSize}
#{bindings.
SomeView
.rangeStart + bindings.
SomeView
.rangeSize}
Current Row Number
#{bindings.
SomeViewIter
.rangeStart + bindings.
SomeViewIter
.currentRowIndexInRange + 1}
#{bindings.
SomeView
.currentRowIndex + 1}
When a view object has named bind variables, an additional ExecuteWithParams
operation appears in the corresponding data collection's Operations folder. As shown in Figure 10-9, this built-in operation accepts one parameter corresponding to each named bind variable. For example, the StaffListByEmailNameRole
view object in the figure has four named bind variables — EmailAddress
, Role
, TheFirstName
, and TheLastName
— so the parameters appear for the ExecuteWithParams
action having these same names. At runtime, when the ExecuteWithParams
built-in operation is invoked by name by the data binding layer, each named bind variable value is set to the respective parameter value passed by the binding layer, and then the view object's query is executed to refresh its row set of results.
An alternative approach, also shown in Figure 10-9, involves creating a custom view object "finder" method that accepts the arguments you want to allow the user to set and then including this method on the client interface of the view object in the View Object Editor. Example 10-1 shows what the code for such a custom method would look like. It is taken from the StaffListByEmailNameRole
view object in the SRDemo application. Notice that due to the application module's active data model, the method does not need to return the data to the client. The method has a void
return type, sets the bind variable values to the parameter values passed into the method using the generated bind variable accessor methods setEmailAddress()
, setRole()
, setTheFirstName()
, and setTheLastName()
. Then, it calls executeQuery()
to execute the query to refresh the view object's row set of results.
Example 10-1 Custom View Object Method Sets Named Bind Variables and Executes Query
// From SRDemo Sample's StaffListByEmailNameRoleImpl.java public void findStaffByEmailNameRole(String email, String firstName, String lastName, String role) { setEmailAddress(email); setRole(role); setTheFirstName(firstName); setTheLastName(lastName); executeQuery(); }
Both of these approaches accomplish the same end result. Both can be used with equal ease from the Data Control Palette to create a search form. The key differences between the approaches are the following:
You don't need to write code since it's a built-in feature.
You can drop the operation from the Data Control Palette to create an ADF Search Form to allow users to search the view object on the named bind variable values.
Your search fields corresponding to the named bind variables inherit bind variables UI control hints for their label text that you can define as part of the view object component.
You need to write a little custom code, but you see a top-level operation in the Data Control Palette whose name can help clarify the intent of the find operation.
You can drop the custom operation from the Data Control Palette to create an ADF Search Form to allow users to search the view object on the parameter values.
Your search fields corresponding to the method arguments do not inherit label text UI control hints you define on the view object's named bind variables themselves. Instead, you need to define UI control hints for their label text by using the Properties... choice from the context menu of the page definition variable corresponding to the method argument as shown in Figure 10-10.
In short, it's good to be aware of both approaches and you can decide for yourself which approach you prefer. As described in Section 10.6.5, "The SRStaffSearch Page", while the StaffListByEmailNameRole
view object contains the above findStaffByEmailNameRole()
custom method for educational purposes, the demo's JSF page uses the declarative ExecuteWithParams
built-in action.
In the ADF Model layer, an iterator binding supports a feature called "find mode" when working with data collections that support query-by-example. As cited in Section 5.8, "Filtering Results Using Query-By-Example View Criteria", view objects support query-by-example when view criteria row set of view criteria rows has been applied. The view criteria rows have the same structure as the rows in the view object, but the datatype of each attribute is String
to allow criteria like "> 304
" or "IN (314,326)
" to be entered as search criteria.
In support of the find mode feature, an iterator binding has a boolean findMode
property that defaults to false
. As shown in Figure 10-11, when findMode
is false
the iterator binding points at the row set containing the rows of data in the data collection. In contrast, when you set findMode
to true
the iterator binding switches to point at the row set of view criteria rows.
In the binding layer, action bindings that invoke built-in data control operations are associated to an iterator binding in the page definition metadata. This enables the binding layer to apply an operation like Create
or Delete
, for example, to the correct data collection at runtime. When an operation like Create
or Delete
is invoked for an iterator binding that has findMode
set to false
, these operations affect the row set containing the data. In contrast, if the operations are invoked for an iterator binding that has findMode
set to true
, then they affect the row set containing the view criteria row, effectively creating or deleting a row of query-by-example criteria.
The built-in Find
operation allows you to toggle an iterator binding's findMode
flag. If an iterator binding's findMode
is false
, invoking the Find
operation for that iterator binding sets the flag to true
. The UI components that are bound to attribute bindings related to this iterator switch accordingly to displaying the current view criteria row in the view criteria row set. If the user modifies the values of UI components while their related iterator is in find mode, the values are applied to the view criteria row in the view criteria row set. If you invoke the Execute
or ExecuteWithParams
built-in operation on an iterator that is in find mode, each will first toggle find mode to false, apply the find mode criteria, and refresh the data collection.
If an iterator binding's findMode
is true
invoking the Find
operation sets it to false
and removes the view criteria rows in the view criteria row set. This effectively cancels the query-by-example mode.
The ADF Controller layer integrates the JSF page lifecycle with the ADF Model data binding layer. You can customize this integration either globally for your entire application, or on a per-page basis. This section highlights some tips related to how the SRDemo application illustrates using both of these techniques.
To globally customize the ADF Page Lifecycle, do the following:
Create a class that extends oracle.adf.controller.faces.lifecycle.FacesPageLifecycle
Create a class that extends ADFPhaseListener
and overrides the createPageLifecycle()
method to return an instance of your custom page lifecycle class.
Change your faces-config.xml
file to use your subclass of ADFPhaseListener
instead of the default ADFPhaseListener
. As shown in Figure 10-12, you can do this on the Overview tab of the JDeveloper faces-config.xml
editor, in the Life Cycle category.
Note:
Make sure to replace the existing ADFPhaseListener with your custom subclass of ADFPhaseListener, or everything in the JSF / ADF lifecycle coordination will happen twice!The SRDemo application includes a SRDemoPageLifecycle
class that globally overrides the reportErrors()
method of the page lifecycle to change the default way that exceptions caught and cached by the ADF Model layer are reported to JSF. The changed implementation reduces the exceptions reported to the user to include only the exceptions that they can directly act upon, suppressing additional "wrapping" exceptions that will not make much sense to the end user.
You can customize the lifecycle of a single page setting the ControllerClass
attribute of the pageDefinition to identify a class that either:
Extends oracle.adf.controller.v2.PageController
class
Implements oracle.adf.controller.v2.PagePhaseListener
interface
The value of the page definition's ControllerClass
attribute can either be:
A fully qualified class name
An EL expression that resolves to a class that meets the requirements above
Using an EL expression for the value of the ControllerClass
, it is possible to specify the name of a custom page controller class (or page phase listener implementation) that you've configured as a managed bean in the faces-config.xml
file. This includes a backing bean for a JSF page, provided that it either extended PageController
or implements PagePhaseListener
.
Figure 10-13 illustrates how to select the root node of the page definition in the Structure window to set.
Note:
When using an EL expression for the value of theControllerClass
attribute, the Structure window may show a warning, saying the "#{
YourExpr
ession}
" is not a valid class. You can safely ignore this warning.The SRDemo application contains a OnPageLoadBackingBeanBase
class in the oracle.srdemo.view.util
package that implements the PagePhaseListener
interface described above using code like what's shown in Example 10-2. The class implements the interface's beforePhase()
and afterPhase()
methods so that in invokes an onPageLoad()
method before the normal ADF prepare model phase, and an onPagePreRender()
method after the normal ADF prepare render phase.
Example 10-2 PagePhaseListener to Invoke an onPageLoad() and onPagePreRender() Method
// In class oracle.srdemo.view.util.OnPageLoadBackingBeanBase /** * Before the ADF page lifecycle's prepareModel phase, invoke a * custom onPageLoad() method. Subclasses override the onPageLoad() * to do something interesting during this event. * @param event */ public void beforePhase(PagePhaseEvent event) { FacesPageLifecycleContext ctx = (FacesPageLifecycleContext)event.getLifecycleContext(); if (event.getPhaseId() == Lifecycle.PREPARE_MODEL_ID) { bc = ctx.getBindingContainer(); onPageLoad(); bc = null; } } /** * After the ADF page lifecycle's prepareRender phase, invoke a * custom onPagePreRender() method. Subclasses override the onPagePreRender() * to do something interesting during this event. * @param event */ public void afterPhase(PagePhaseEvent event) { FacesPageLifecycleContext ctx = (FacesPageLifecycleContext)event.getLifecycleContext(); if (event.getPhaseId() == Lifecycle.PREPARE_RENDER_ID) { bc = ctx.getBindingContainer(); onPagePreRender(); bc = null; } } public void onPageLoad() { // Subclasses can override this. } public void onPagePreRender() { // Subclasses can override this. }
If a managed bean extends the OnPageLoadBackingBeanBase
class, then it can be used as an ADF page phase listener because it inherits the implementation of this interface from the base class. If that backing bean then overrides either or both of the onPageLoad()
or onPagePreRender()
method, that method will be invoked by the ADF Page Lifecycle at the appropriate time during the page request lifecycle. The last step in getting such a backing bean to work, is to tell the ADF page definition to use the backing bean as its page controller for that page. As described above, this is done by setting the ControllerClass
attribute on the page definition in question to an EL expression that evaluates to the backing bean.
The SRMain
page in the SRDemo application uses the technique described in this section to illustrate writing programmatic code in the onPageLoad()
method of the SRMain
backing bean in the oracle.srdemo.view.backing
package. Since that backing bean is named backing_SRMain
in faces-config.xml
, the ControllerClass
property of the SRMain
page's page definition is set to the EL expression "#{backing_SRMain}
".
Figure 10-14 illustrates how the JSF page lifecycle relates to the extended page processing phases that the ADF page lifecycle adds. You can use the Refresh
property on iterator bindings and invokeAction
executables in your page definition to control when each are evaluated during the ADF page lifecycle, either during the prepareModel phase, the prepareRender phase, or both. Since the term "refresh" isn't exactly second-nature, this section clarifies what it means for each kind of executable and how you should set their Refresh
property correctly to achieve the behavior you need.
In practice, when working with iterator bindings for view object instances in an application module, you can simply leave the default setting of Refresh="ifNeeded
". You may complement this with a boolean-valued EL expression in the RefreshCondition
property to conditionally avoid refreshing the iterator if desired. However, you may still be asking yourself, "What does refreshing an iterator binding mean anyway?"
An iterator binding, as its name implies, is a binding that points to an iterator. For scalability reasons, at runtime the iterator bindings in the binding container release any reference they have to a row set iterator at the end of each request. During the next request, the iterator bindings are refreshed to again point at a "live" row set iterator that is tracking the current row of some data collection. The act of refreshing an ADF iterator binding during the ADF page lifecycle is precisely the operation of accessing the row set iterator to "reunite" the binding to the row set iterator to which it is bound.
If an iterator binding is not refreshed during the lifecycle, it is not pointing to any row set iterator for that request. This results in the value bindings related to that iterator binding not having any data to display. This can be a desirable result, for example, if you want a page like a search page to initially show no data. To achieve this, you can use the RefreshCondition
of:
#{adfFacesContext.postback == true}
The adfFacesContext.postback
boolean property evaluates to false
when a page is first rendered, or rendered due to navigation from another page. It evaluates to true
when the end user has interacted with some UI control on the page, causing a postback to that page to handle the event. By using this expression as the RefreshCondition
for a particular iterator binding, it will refresh the iterator binding only when the user interacts with a control on the page.
The valid values for the Refresh
property of an iterator binding are as follows. If you want to refresh the iterator during:
Both the prepareModel and prepareRender phases, use Refresh="ifNeeded
" (default)
Just during the prepareModel phase, use Refresh="prepareModel
"
Just during the prepareRender phase, use Refresh="renderModel
"
If you only want the iterator binding to refresh when your own code calls getRowSetIterator()
on the iterator binding, set Refresh="never
". Other values of Refresh
are either not relevant to iterator bindings, or reserved for future use.
It is important to understand that when working with iterator bindings related to a view object instance in an application module, refreshing an iterator binding does not forcibly re-execute its query each time. The first time the view object instance's row set iterator is accessed during a particular user's unit of work, this will implicitly execute the view object's query if it was not already executed. Subsequent refreshing of the iterator binding related to that view object instance on page requests that are part of the same logical unit of work will only access the row set iterator again, not forcibly re-execute the query. Should you desire re-executing the query to refresh its data, use the Execute
or ExecuteWithParams
built-in operation, or programmatically call the executeQuery()
method on the iterator binding.
Several page definitions in the SRDemo application use the declarative invokeAction
binding to trigger either built-in operations or custom operations during this extended ADF page processing lifecycle. Each invokeAction
has an id
property that give the binding a name, and then three other properties of interest:
The Binds
property controls what the invokeAction
will do if it fires
Its value is the name of an action binding or method action binding in the same binding container.
The Refresh
property controls when the invokeAction
will invoke the action binding
To have it fire during the ADF page lifecycle's:
prepareModel phase, use Refresh=prepareModel
prepareRender phase, use Refresh=renderModel
prepareModel and prepareRender phases, use Refresh=ifNeeded
The RefreshCondition
property can be used to control whether it will fire at all
Its value, if supplied, is a boolean-valued EL expression. If the expression evaluates to true
when the invokeAction
is considered during the page lifecycle, the related action binding is invoked. If it evaluates to false
, then the action binding is not invoked.
Note:
Other values of theRefresh
property not described here are reserved for future use.Notice in Figure 10-14 that the key distinction between the ADF prepareModel phase and the prepareRender phase is that one comes before JSF's invokeApplication phase, and one after. Since JSF's invokeApplication phase is when action listeners fire, if you need your invokeAction
to trigger after these action listeners have performed their processing, you'll want to use the Refresh="renderModel
" setting on it.
If the invokeAction
binds to a method action binding that accepts parameters, then two additional values can be supplied for the Refresh
property: prepareModelIfNeeded
and renderModelIfNeeded
. These have the same meaning as their companion settings without the *IfNeeded
suffix, except that they perform an optimization to compare the current set of evaluated parameter values with the set that was used to invoke the method action binding the previous time. If the parameter values for the current invocation are exactly the same as the ones used previously, the invokeAction
does not invoke its bound method action binding.
Note:
The default value of theRefresh
property is ifNeeded
. This means that if you do not supply a RefreshCondition
expression to further refine its firing, the related action binding will be invoked twice during each request. Oracle recommends either adding an appropriate RefreshCondition
expression (if you want it evaluated during both phases) or changing the default Refresh setting for invokeAction bindings to either prepareModel
or renderModel
, depending on whether you want your invokeAction
to occur before or after the JSF invokeApplication
phase.You can call the getKey()
method on any view row get a Key
object that encapsulates the one or more key attributes that identify the row. As you've seen in various examples, you can also use a Key
object like this to find a view row in a row set using the findByKey()
. At runtime, when either the setCurrentRowWithKey
or the setCurrentRowWithKeyValue
built-in operations is invoked by name by the data binding layer, the findByKey()
method is used to find the row based on the value passed in as a parameter before setting the found row as the current row.
Confusingly, as shown in Figure 10-15, the setCurrentRowWithKey
and setCurrentRowWithKeyValue
operations both expect a parameter named rowKey
, but they differ precisely by what each expects that rowKey
parameter value to be at runtime:
setCurrentRowWithKey
expects the rowKey
parameter value to be the serialized string representation of a view row key. This is a hexadecimal-encoded string that looks like this:
000200000002C20200000002C102000000010000010A5AB7DAD9
The serialized string representation of a key encodes all of the key attributes that might comprise a view row's key in a way that can be conveniently passed as a single value in a browser URL string or form parameter. At runtime, if you inadvertently pass a parameter value that is not a legal serialized string key, you may receive exceptions like oracle.jbo.InvalidParamException
or java.io.EOFException
as a result. In your web page, you can access the value of the serialized string key of a row by referencing the rowKeyStr
property of an ADF control binding (e.g. #{bindings.SomeAttrName.rowKeyStr}
) or the row variable of an ADF Faces table (e.g. #{row.rowKeyStr}
).
setCurrentRowWithKeyValue
expects the rowKey
parameter value to be the literal value representing the key of the view row. For example, it's value would be simply "201
" to find service request number 201
.
Note:
If you write custom code in an application module class and need to find a Row based on a serialized string key passed from the client, you can use thegetRowFromKey()
method in the JboUtil
class in the oracle.jbo.client
package:
static public Row getRowFromKey(RowSetIterator rsi, String sKey)
Pass the view object instance in which you'd like to find the row as the first parameter, and the serialized string format of the Key as the second parameter.
An application module provides a feature called bundled exception mode which allows web applications to easily present a maximal set of failed validation exceptions to the end user, instead of presenting only the first error that gets raised. By default, the ADF Business Components application module pool enables bundled exception mode for web applications.
You typically will not need to change this default setting. However it is important to understand that it is enabled by default since it effects how validation exceptions are thrown. Since the Java language and runtime only support throwing a single exception object, the way that bundled validation exceptions are implemented is by wrapping a set of exceptions as details of a new "parent" exception that contains them. For example, if multiple attributes in a single entity object fail attribute-level validation, then these multiple ValidationException
objects will be wrapped in a RowValException
. This wrapping exception contains the row key of the row that has failed validation. At transaction commit time, if multiple rows do not successfully pass the validation performed during commit, then all of the RowValException
objects will get wrapped in an enclosing TxnValException
object.
When writing custom error processing code, as illustrated by the overridden reportErrors()
method in the SRDemoPageLifecycle
class in the SRDemo application, you can use the getDetails()
method of the JboException
base exception class to recursively process the bundled exceptions contained inside it.
Note:
All the exception classes mentioned here are in theoracle.jbo
package.This section provides a brief overview of how each page in the SRDemo application uses the SRService
application module's view object instances and service methods.
Figure 10-16 illustrates the data binding for SRList
page.
ServiceRequestsByStatusIterator
for ServiceRequestsByStatus
view object instance
None
setCurrentRowWithKey
, ExecuteWithParams
related to the ServiceRequestsByStatusIterator
iterator binding
None
None
ServiceRequestsByStatus
is an instance of the entity-based view object ServiceRequestsByStatus
, which extends the ServiceRequests
view object and adds a named bind variable called StatusCode
.
None
Figure 10-17 illustrates the data binding for the SRMain
page.
ServiceRequestsIterator
for ServiceRequests
view object instance
ServiceHistoriesIterator
for ServiceHistories
view object instance
None
setCurrentRowWithKey
, Delete
related to the ServiceRequestsIterator
iterator binding
Create
, DeleteNewHistory
(for Delete
built-in) related to the ServiceHistoriesIterator
iterator binding
Commit
related to the SRService
data control
deleteServiceHistoryNotes
invokes deleteServiceHistoryNotes()
method on the SRService
client interface
None
Note:
TheSRMain
backing bean for the SRMain
page (in the oracle.srdemo.view.backing
package) is using the technique described in Section 10.5.4.3, "Using Custom ADF Page Lifecycle to Invoke an onPageLoad Backing Bean Method" to programmatically accomplish exactly the same thing that the SREdit
page is doing declaratively.ServiceRequests
is an instance of the entity-based view object ServiceRequests
. It joins data from the main ServiceRequest
entity usage and three additional reference entity usages: CreatedByUser
(User
entity object), AssignedToUser
(User
entity object), and Product
. The ServiceRequests
view object is linked master/detail to the ServiceHistories
view object.
ServiceHistories
is an instance of the entity-based view object ServiceHistories
. It joins data from the main ServiceHistory
entity usage and an additional reference entity usage SystemUser
(User
entity object). It is an XML-only view object, with no custom Java class.
As shown in Example 10-3, the deleteServiceHistoryNotes()
method deletes service history note rows corresponding to the Key
objects in the key set passed as an argument.
Example 10-3 DeleteServiceHistoryNotes Method in SRServiceImpl.java
public void deleteServiceHistoryNotes(Set keySet) { if (keySet != null) { ViewObject histories = getServiceHistories(); for (Key k: (Set<Key>)keySet) { Row[] rowToDelete = histories.findByKey(k, 1); if (rowToDelete == null || rowToDelete.length == 0) { throw new JboException("Failed to find row with serialized key" + k.toStringFormat(false)); } rowToDelete[0].remove(); getDBTransaction().commit(); } } }
Figure 10-18 illustrates the data binding in the SREdit
page.
ServiceRequestsIterator
for ServiceRequests
view object instance
ServiceRequestStatusListIterator
for ServiceRequestStatusList
view object instance
None
setCurrentRowWithKey
related to the ServiceRequestsIterator
iterator binding
Commit
related to the SRService
data control
cancelEditsToCurrentServiceRequest
invokes cancelEditsToCurrentServiceRequest()
on the SRService
client interface
setRowToEditFromRequestParameter
invokes the built-in setCurrentRowWithKey
operation in prepare model phase (Refresh="prepareModel
") when first navigating to the page (i.e. not processing a postback) and processScope.rowKeyStr
attribute is not set (RefreshCondition="${adfFacesContext.postback == false and not empty processScope.rowKeyStr}
").
ServiceRequests
is an instance of the entity-based view object ServiceRequests
. It joins data from the main ServiceRequest
entity usage and three additional reference entity usages: CreatedByUser
(User
entity object), AssignedToUser
(User
entity object), and Product
.
ServiceRequestStatusList
is an instance of the read-only view object ServiceRequestStatusList
. Its data is provided by a static list supplied in the ServiceRequestStatusListImpl.java
class. This class extends a SRStaticDataViewObjectImpl
in the FrameworkExtensions
project which provides the basic support for implementing a view object based on static data.
As shown in Example 10-4, the cancelEditsToCurrentServiceRequest()
method uses the refresh()
method to cancel edits made in the current transaction to the current service request row.
Figure 10-19 illustrates the data binding in the SRSearch
page.
SearchServiceRequestsIterator
for SearchServiceRequests
view object instance (Forced to stay in find mode by AlwaysFind
invokeAction).
SearchServiceRequestsResultsIterator
for SearchServiceRequests
view object instance
ServiceRequestStatusListIterator
for ServiceRequestStatusList
view object instance
None
Execute
, Delete
, Create
related to the SearchServiceRequestsIterator
iterator binding
Find
, First
, Next
, Previous
, Last
, setCurrentRowWithKey
related to the SearchServiceRequestsResultsIterator
iterator binding
None
AlwaysFind
invokes the built-in Find
operation (for the SearchServiceRequestsIterator
iterator binding) in either prepare model or render model phases (Refresh="ifNeeded
") when the SearchServiceRequestsIterator
is not in find mode ( ${bindings.SearchServiceRequestsIterator.findMode == false}
)
insertBlankViewCriteriaRowIfThereAreNone
invokes the built-in Create
operation in either prepare model or render model phases (Refresh="ifNeeded
") when the SearchServiceRequestsIterator
is not in find mode ( ${bindings.SearchServiceRequestsIterator.findMode == false}
)
SearchServiceRequests
is an instance of the entity-based view object ServiceRequests
. It joins data from the main ServiceRequest
entity usage and three additional reference entity usages: CreatedByUser
(User
entity object), AssignedToUser
(User
entity object), and Product
.
ServiceRequestStatusList
is an instance of the read-only view object ServiceRequestStatusList
. Its data is provided by a static list supplied in the ServiceRequestStatusListImpl.java
class. This class extends a SRStaticDataViewObjectImpl
in the FrameworkExtensions
project which provides the basic support for implementing a view object based on static data.
None
Figure 10-20 illustrates the data binding for the SRStaffSearch
page.
StaffListByEmailNameRoleIterator
for StaffListByEmailNameRole
view object instance
StaffListByEmailNameRole_Role
, StaffListByEmailNameRole_EmailAddress
, StaffListByEmailNameRole_TheFirstName
, StaffListByEmailNameRole_TheLastName
ExecuteWithParams
related to StaffListByEmailNameRoleIterator
iterator binding
None
None
StaffListByEmailNameRole
is an instance of the entity-based view object StaffListByEmailNameRole
, which extends the StaffList
view object and adds name bind variables EmailAddress
, Role
, TheFirstName
, and TheLastName
.
None
Figure 10-21 illustrates the data binding in the SRManage
page.
StaffWithOpenRequestsIterator
for StaffWithOpenRequests
view object instance
ExpertiseAreasIterator
for ExpertiseAreas
view object instance
OpenOrPendingServiceRequestsIterator
for OpenOrPendingServiceRequests
view object instance
ServiceHistoriesForRequestIterator
for ServiceHistoriesForRequest
view object instance
None
setCurrentStaffRowWithKey
(for setCurrentRowWithKey
built-in) related to StaffWithOpenRequestsIterator
iterator binding
setCurrentProblemAndAssigneeRows
invokes setCurrentProblemAndAssigneeRows()
on the SRService
client interface
None
StaffWithOpenRequests
is an instance of the read-only view object StaffWithOpenRequests
. It queries information from the USERS
and SERVICE_REQUESTS
tables. It is an XML only view object, with no related Java class. This view object is linked master/detail with the ExpertiseAreas
and OpenOrPendingServiceRequests
view objects.
ExpertiseAreas
is an instance of the entity-based view object ExpertiseAreas
. It joins information from the primary ExpertiseArea
entity usage and a reference Product
entity usage. It is an XML only view object, with no related Java class.
OpenOrPendingServiceRequests
is an instance of the read-only view object OpenOrPendingServiceRequests
. It queries information from the SERVICE_REQUESTS
table. It is an XML only view object, with no related Java class. This view object is linked master/detail with the ServiceHistories
view object.
ServiceHistoriesForRequest
is an instance of the entity-based view object ServiceHistories
. It joins data from the main ServiceHistory
entity usage and an additional reference entity usage SystemUser
(User
entity object). It is an XML-only view object, with no custom Java class.
As shown in Example 10-5, the setCurrentProblemAndAssigneeRows()
method uses a helper method to set the current row in the StaffWithOpenRequests
view object instance and the OpenOrPendingServiceRequests
view object instance based on the serialized string keys passed in.
Figure 10-21 illustrates the data binding for the SRSkills
page.
StaffListIterator
for StaffList
view object instance
StaffExpertiseAreasIterator
for StaffExpertiseAreas
view object instance
ProductListIterator
for ProductList
view object instance
None
None
updateSkillsForCurrentStaff
invokes the updateSkillsForCurrentStaff()
method on the SRService
client interface
None
StaffList
is an instance of the entity-based view object StaffList
. It queries information from the primary entity usage SystemUser
(User
entity object). This view object is linked master/detail with the ExpertiseAreas
view object.
StaffExpertiseAreas
is an instance of the entity-based view object ExpertiseAreas
. It joins information from the primary ExpertiseArea
entity usage and a reference Product
entity usage. It is an XML only view object, with no related Java class.
ProductList
is an instance of the entity-based view object ProductList
. It queries data from the primary entity usage Products
(Product
entity object). It is an XML only view object, with no related Java class.
As shown in Example 10-6, the updateSkillsForCurrentStaff()
method performs the following steps:
Clones the list of product ids
Creates a secondary RowSetIterator
to do programmatic iteration over the ExpertiseAreas
Removes rows for current user for products that are not in the list of products ids
Closes the secondary row set iterator when done iterating
Adds new rows for the keys that are left
Commits the transaction.
Example 10-6 UpdateSkillsForCurrentStaff Method in SRServiceImpl.java
public void updateSkillsForCurrentStaff(List productIds) { if (productIds != null && productIds.size() > 0) { // 1. Cone the list of product ids List<Number> copyOfProductIds = (List<Number>)Utils.cloneList(productIds); ViewObject skills = getStaffExpertiseAreas(); // 2. Create a secondary rowset iterator for iteration RowSetIterator rsi = skills.createRowSetIterator(null); // 3. Remove rows for current user for products not in list of products while (rsi.hasNext()) { Row r = rsi.next(); Number productId = (Number)r.getAttribute("ProdId"); // if the existing row is in the list, we're ok, so remove from list. if (copyOfProductIds.contains(productId)) { copyOfProductIds.remove(productId); } // if the existing row is in not list, remove it. else { r.remove(); } } // 4. Close the secondary row set iterator when we're done rsi.closeRowSetIterator(); // 5. Add new rows for the keys that are left for (Number productIdToAdd: copyOfProductIds) { Row newRow = skills.createRow(); skills.insertRow(newRow); newRow.setAttribute("ProdId", productIdToAdd); } // 6. Commit the transaction getDBTransaction().commit(); } }
Note:
Since the iterator bindings in the ADF Model layer bind by default to the default row set iterator for the default rowset of the view object instance to which they are related, it's best practice to create a secondary row set iterator to perform programmatic iteration of a view object's row set in your application business logic. This way you do not affect the current row that the user sees in the user interface. The secondary iterator can have a developer-assigned name, but if you passnull
the system assigns it a name. Since you'll typically always be closing it as soon as you're done iterating, using the system-assigned name is fine.Figure 10-23 illustrates the data binding for the SRCreate
page.
GlobalsIterator
for Globals
view object instance
ProductListIterator
for ProductList
view object instance
None
None
cancelNewServiceRequest
invokes the cancelNewServiceRequest
method on the SRService
client interface
clearServiceRequestFieldsIfNotInTrain
invokes the cancelNewServiceRequest
method action binding during prepare model phase (Refresh="prepareModel
") when the page is not handling postback events and the requestScope.processChoice
attribute is not set (RefreshCondition="${adfFacesContext.postback == false and empty requestScope.processChoice}
").
ProductList
is an instance of the entity-based view object ProductList
. It queries data from the primary entity usage Products
(Product
entity object). It is an XML only view object, with no related Java class.
Globals
is an instance of the transient view object Globals
. It contains transient attributes to temporarily store information about ProductId
, ProductName
, and ProductDescription
across pages.
Note:
A transient view object is one that has no SQL query and all transient attributes. It can contain rows that are populated either programmatically by your application module business logic or declaratively using action bindings for built-in operations in the ADF Model layer. For users familiar with Oracle Forms, this is similar to what you know as a "non database block" in Forms.As shown in Example 10-7, the cancelNewServiceRequest()
method ensures there is a single blank row in the Globals
view object instance.
Figure 10-24 illustrates the data binding for the SRConfirmCreate
page.
GlobalsIterator
for Globals
view object instance
LoggedInUserIterator
for LoggedInUser
view object instance
None
None
cancelNewServiceRequest
invokes the cancelNewServiceRequest()
on the SRService
client interface
createServiceRequest
invokes the createServiceRequest()
on the SRService
client interface
None
Globals
is an instance of the transient view object Globals
. It contains transient attributes to temporarily store information about ProductId
, ProductName
, and ProductDescription
across pages.
LoggedInUser
is an instance of the entity-based view object LoggedInUser
. It queries data from the primary entity usage Users
(User
entity object). The LoggedInUser
view object is linked master/detail with the ServiceRequestsByStatus
view object.
As shown in Example 10-8, the createServiceRequest()
method performs the following basic steps:
Gets the entity definition for ServiceRequest
Creates a new ServiceRequest
entity row (ServiceRequestImpl
class)
Accesses the current row in Globals
as a strongly-typed GlobalsRowImpl
Set the problem description and product ID for new service request
Commits the transaction
Returns an Integer representing the database-trigger assigned SR number for the new service request.
Example 10-8 CreateServiceRequest Method in SRServiceImpl.java
public Integer createServiceRequest() { // 1. Get the entity definition for ServiceRequest EntityDefImpl svcReqDef = ServiceRequestImpl.getDefinitionObject(); // 2. Create a new ServiceRequest entity row ServiceRequestImpl newReq = (ServiceRequestImpl)svcReqDef.createInstance2(getDBTransaction(),null); // 3. Access the current row in Globals as a strongly-typed GlobalsRowImpl GlobalsRowImpl globalsRow = (GlobalsRowImpl)getGlobals().getCurrentRow(); // 4. Set the problem description and product id for new service request newReq.setProblemDescription(globalsRow.getProblemDescription()); newReq.setProdId(globalsRow.getProductId()); // 5. Commit the transaction getDBTransaction().commit(); // 6. Return an integer representing the database-assigned SR Number return newReq.getSvrId().getSequenceNumber().intValue(); }