Written By Duncan Mills, Oracle Corporation
Oracle JDeveloper 10g, using the Oracle Application Development Framework (ADF) provides the JSP page developer with out of the box functionality for binding input forms on JSP pages to business service data. The development environment and framework allow the developer to create HTML rendered forms capable of full Create, Update and Delete functionality with no coding using a combination of the ADF model layer metadata and specialized Struts Actions which automatically handle the data transfer, record navigation and transactional state of the screen for the developer.
However, this code free solution is based around single row input for a record set. This paper covers the situation where the developer is tasked with creating a more sophisticated multi-row input form often emulating a grid or spread sheet type of interface on a JSP based web page. Although this is a less traditional interface for web based applications it is becoming more popular as desktop style systems are migrated or emulated on HTML client deployment.
Here is an example of the style of screen that this article is discussing.
Figure 1 - JSP screen with multi-row update capability
Screens like this may consist of one or more updateable columns mixed in with read only data, the technique readily handles any such mix.
The Apache Struts controller, which is responsible for the page flow in most ADF applications, already has the functionality to handle multi-row input and make that data available to the developer in a simple to consume fashion, although it must be stressed that the responsibilities of Struts stop with making the information available and that framework provides no model layer integration to further help the developer reconcile that data with their data store.
Once Struts has marshaled the data off of the request into an array suitable for processing we can override the relevant part of the ADF Data Action lifecycle to handle the processing of all of the updated rows at once rather than the single update row that it normally dealt with. This customized method can feed the updated data to the ADF model layer in a controlled way and all of the other default functionality of the ADF Model / Struts integration such as record scrolling and transactional state are maintained.
The implementation as it stands requires a certain amount of coding for each multi-row update form that has to be created, it is not at this stage a generic framework component that can be driven by metadata.
The implementation in this example, is based around the use of ADF Business Components as the business service provider, although the principles are extendable to any updateable collection exposed through the ADF model.
This document will take you through the process of building a multi-row update screen for the Employees table from the standard Oracle HR demo schema. The screen will allow the user to update the salary and commission columns of the Employees table only. You can download the completed workspace from here.
The first step of the process is to define, in your ViewController project, a simple JavaBean to represent those attributes that will be updateable on the multi-row form. This bean does not have to extend any of the Struts Bean classes such as ActionForm or DynaActionForm, it can simply extend Object. The bean that is defined needs to have one String member variable for each of the updateable attributes in the View Object. This member variable needs to have both a getter and setter and have the same name as the View Object attribute. For example, for the View Object attribute "Salary", this JavaBean should have a member variable called "salary" and a corresponding getSalary() and setSalary() method. The conversion from String to the correct data type for the attribute will be handled by the framework.
As well as the attributes that you wish to update, you will need to add an extra attribute to take the row identifier that will match the update to the correct row in the collection. This should implemented in your JavaBean as a member variable called rowKeyStr, of type String and with the corresponding getRowKeyStr() and setRowKeyStr() methods.
Here is the code for the JavaBean which will be used to support the multi row update of the Salary and CommissionPct columns in the employees table.
package multiRowUpdate.view; public class UpdateRowBean
In addition to the basic row-transfer bean you will, in most cases, have to create a corresponding BeanInfo class to help Struts correctly map to the updateable attributes on the screen. You will not have to carry out this step if all of the multi-row updateable attributes in the View Object are named with an lower case first letter. For instance if the View Object attribute for commission is called "commissionPct" as opposed to "CommissionPct". However, in most View Objects, the attributes will be init-capped so you will have to create the BeanInfo class.
The purpose of the BeanInfo class is to map the attribute names as exposed though the View Object to be mapped to the getters and setters defined in your row transfer bean. In the default JavaBean implementation, a setter called setSalary() in the transfer bean would map to a request attribute of "salary" posted from the page. In this case if our attribute names are initcapped e.g."Salary" this would not match, and the setter would never be called. The BeanInfo allows us to tell the introspection mechanism that it's valid to call "setSalary" for a parameter called "Salary" with an uppercase initial letter.
To create the BeanInfo Class, you need to create a new Java class with the name <transferbeanclass>BeanInfo e.g. UpdateRowBeanBeanInfo, which extends java.beans.SimpleBeanInfo. This class should be in the same package as the row transfer bean. The class needs a single public method called getPropertyDescriptors() which returns an array of PropertyDescriptor objects, one for each attribute (plus one for the rowKeyStr). Note that JDeveloper has a BeanInfo entry in the General > JavaBeans branch of the New Gallery to help you with the creation of the BeanInfo class.
Inside the getPropertyDescriptors() method you need to create a PropertyDescriptor for each of the attributes, passing the name of the attribute in the same case as defined in the View Object, a reference to the row transfer bean class and the named getter and setter to handle that attribute in the transfer bean class.
Here is the BeanInfo class for the above row transfer bean:
The row transfer bean that you have created, will be populated with the values entered for a particular row in the screen. You now need to create a Struts Form-Bean definition which will tell the Struts controller that it needs to handle the page update using an array of your custom row transfer beans. No additional code is required for this step just a form-bean definition in the Struts configuration file. You can create this either using the structure pane and property inspector, or you can add the definition directly to the XML:
<form-bean name="MultiRowUpdateForm" type="org.apache.struts.action.DynaActionForm"> <form-property type="multiRowUpdate.view.UpdateRowBean"
The important element here is the <form-property> which defines that the Form-Bean should be made of an array of UpdateRowBean objects (the transfer bean created above), in this case with 10 elements ( as defined by the size attribute). The size of the array should match the size of your table which is controlled by the Range Size property of the data collection iterator which has a default value of 10 rows. It is also important that the name attribute of the <form-property> is set to Row. This has to match the value of the var attribute of the <c:forEach> loop in the update page, which will be created as the value Row when you create an ADF data bound table. .
Creating the screen involves the initial following tasks
Next we have to link the form-bean that Struts will use to process user input from the page, with the DataPage action that will need access to that data. In the page flow diagram, or the structure pane, select the DataPage that supports the multi-row update form. Set it's name property to the name of the form-bean that you created earlier (e.g. MultiRowUpdateForm) and set the scope property to request.
Figure 6 - Assigning the custom form-bean to the DataPage
As mentioned in the introduction, the DataPages (and DataActions) are set up to process single row updates, so we have to add a little extra code to handle a multi-row array of updates. To do this you need to subclass the DataPage. The simplest way of doing this is to select the action on the page flow and choose Go To Code from the context menu. Once you have a DataPage subclass you need to override the processUpdateModel() method. Struts will already have done most of the work for you and marshaled the data from the Form fields into an array of UpdateRowBeans. You will now have to loop through these rows, check to see which contain real updates and then apply those updates.
Here are the various steps that have to be implemented:
The code for all of this is not complex, the major things that you have to watch for are data type conversion errors due to invalid user input, and deciding how you should handle Empty input. Let's look at each step in turn for the Salary / Commission update screen.
Native Struts functionality has done most of the hard work for us here, and has marshaled the data from the indexed input fields (Salary, CommissionPct and rowKeyStr) into an array of our data transfer beans, in this example called multiRowUpdate.view.UpdateRowBean.
Here's the code fragment:
//Get the array of beans created by Struts from the Form
The ctx variable is the DataActionContext that is passed by the framework into lifecycle methods such as processUpdateModel. This context variable gives you access to the request and other useful Struts artifacts such as the ActionMapping and the form-bean. We use ctx.getActionForm() to get hold of the form-bean that Struts has populated from the page. The array of UpdateRowBean objects is then extracted from that form bean. So we now have an array of "row" objects that can be processed.
The following code will return us a list object containing the actual model rows.
//Get the original rowset
The DCFindSpelObject programmatically references the same data as the <c:forEach> loop which drives the table on the JSP page and uses the same expression (minus the bindings keyword) as that tag
The Struts form-bean will have supplied you with an array of your row transfer beans with whatever size was defined in the <form-bean> definition. You will always receive this number of rows, no matter how many rows are actually displayed on the screen or how many rows the user has changed. So the code at this stage that loops through the array needs to handle that by checking the submitted values against the original rowset, matching on the rowKeyStr.
// Now loop through the bean array
We extract the original row that should match the updated row and compare it's rowKey with the key that came with the submitted row. If they match we can then start to examine individual attributes to see which need to be updated.
Within the loop you now have both the updated values from the UI and the current values stored in the model. All that you have to do now is to compare the two and decide, for each attribute, if the model needs to be updated. This is the part of the process where you have to code defensively. The user may have entered invalid values in the UI, for instance, a string for a number field, so the code should anticipate the possible inputs and either ignore bad values, or raise an appropriate error.
You may find it simplest to create a convenience method that handles the job of comparing the new and old values, updating the model if required. Here is a simple example which you can adapt. Note that for brevity this version does not do any defensive error handling - you should do so in your version.
private void updateIfReqd(JUCtrlValueBindingRef baseRow, String attr, String newVal)
Using this convenience method, all the attributes can be tested and pushed to the model if changed.
//Process each attribute updateIfRequired(oldRow,"Salary",updatedRows[i].getSalary());
If changes were applied to the model based on the input data, then you need to validate those changes so as to catch any validation checks applied by the business service. This validation will include (in the ADF Business Components case) things like precision errors and declarative validation rules created against the underlying entity object. This code overrides another lifecycle method and just has to defer to the data control to handle the validation if it is required.
protected void validateModelUpdates(DataActionContext ctx)
Note the use of isDirty() and setDirtyState() which are private convenience methods used to maintain a flag indicating that validation is required.
For reference here is the complete code for the employees example, including the error handling routines. You should adapt and improve this code according to the needs of the input screen in question. For instance, you may want to add more upfront validation to the attributes before they are submitted to the model, so that you have more control over the display and content of error messages.
package multiRowUpdate.view; import org.apache.struts.action.DynaActionForm; import oracle.adf.controller.struts.actions.DataActionContext; import oracle.adf.controller.struts.actions.DataForwardAction; import oracle.adf.model.binding.DCUtil; import oracle.jbo.uicli.binding.JUCtrlValueBindingRef;
Problem: Running the page results in a Server 500 error and an message like:
javax.servlet.jsp.JspException: Cannot find bean <name> in any scope.
Solution: Check all of your updateable attributes and the hidden rowKeyStr item and make sure that the name property matches the Name property of the <form-property> element under the Struts form-bean that you created. This value should normally be Row, a value of "row" in the JSP page would cause this error..
Problem:The page runs but the enterable fields are empty even though there is data in the table.
Solution: The value of the property attribute of the <html:text> tag needs to match the View Object Attribute exactly. Check that the value in the JSP is in the same case as the View Object attribute.
Problem:When the page is submitted the columns come back with the old values and no error is shown.
Solution: You are probably suffering from a naming mismatch between the attributes as used in the property= attribute of the <html:text> tag and the row transfer bean. You may need a BeanInfo class to handle the naming mismatch.
Problem: When the page is submitted the following error is displayed: JBO-29000 - Unexpected Exception - java.lang.UnsupportedOperationException
Solution: Check the names of the attributes in your processUpdateModel() method of the DataPage. If you use an incorrectly named attribute in the put() call on the model row this error can happen.
Problem: When the page is submitted a validation error is shown, but the grid now shows the original values not the value that caused the error.
Solution: See the restrictions above. You will have to add some manual validation checking if you want to capture this.
Hopefully this article has shown how ADF based JSP applications can be extended to provide sophisticated multi-row user input with little custom coding. More formal support for multi-row input will appear in future versions of the framework, but in the meantime this solution can be used to satisfy your multi-row input needs.
drmills v1.2 07/July/2004