Customizing the default resolver logic

As shown in the DataExpress diagram, JBuilder makes it easy to write a custom resolver for your data when you are accessing data from a custom data source, such as SAP, BAAN, IMS, OS/390, CICS, VSAM, DB2, etc.

The retrieval and update of data from a data source, such as an Oracle or Sybase server, is isolated to two key interfaces: provider/resolver. Providers populate a data set from a data source. Resolvers save changes back to a data source. By cleanly isolating the retrieval and updating of data to two interfaces, it is easy to create new provider/resolver components for new data sources. There are two provider/resolver implementations for standard JDBC drivers that provide access to popular databases such as support for Oracle, Sybase, Informix, InterBase, DB2, MS SQL Server, Paradox, dBASE, FoxPro, Access, and other popular databases. In the future, Borland and third parties can create custom provider/resolver component implementations for EJB, application servers, SAP, BAAN, IMS, CICS, etc.

Please see the JBuilder documentation Web site at http://www.borland.com/techpubs for more information on this topic and possibly a tutorial. An example project with a custom provider/resolver may be available in the jbuilder/samples/borland/samples/tutorial/dataset/providers directory. For an example of writing a customer ProcedureResolver, see "Tutorial: saving changes with a ProcedureResolver" in this chapter.

If you have not specifically instantiated a QueryResolver component when resolving data changes back to the data source, the built-in resolver logic creates a default QueryResolver component for you. This topic explores using the QueryResolver to customize the resolution process.

The QueryResolver is a JBCL component which implements the SQLResolver interface. It is this SQLResolver interface which is used by the ResolutionManager during the process of resolving changes back to the database. As its name implies, the ResolutionManager class manages the resolving phase.

Each StorageDataSet has a resolver property. This resolver property must refer to an object implementing the SQLResolver interface. If this property has not been set when you call the Database.saveChanges() method, it creates a default QueryResolver and attempts to save the changes for a particular DataSet.

To intercept the events involved in resolution, you must provide your own SQLResolver object for a DataSet. The QueryResolver component implements SQLResolver and provides an existing framework that you can modify.

Adding a QueryResolver component

To add a QueryResolver component to your application using the JBuilder visual design tools:
  1. Open an existing project that you want to add custom resolver logic to. The project should include a Database object, and a QueryDataSet object. See Querying a database for how to do this.
  2. Select the Frame class and click the Design tab to display the UI Designer.
  3. Click the QueryResolver component (shown here) from the Data Express tab of the Component Palette. .
  4. Click (anywhere) in the Component tree or the UI Designer to add it to your application.
The UI Designer generates source code that creates a default QueryResolver object (named queryResolver1 by default) that looks like this:
     queryDataSet1.setResolver(queryResolver1);

You connect the QueryResolver to the appropriate DataSet by setting the resolver property of that DataSet to an instantiated QueryResolver, for example, queryResolver1. You can connect the same QueryResolver to more than one DataSet if the different DataSet objects share the same event handling. If each DataSet needs custom event handling, create a separate QueryResolver for each DataSet.

Resolver events

You control the resolution process by intercepting QueryResolver events. When the QueryResolver object is selected in the Component tree, the Events tab of the Inspector displays its events. The events that you can control (defined in the ResolverListener interface) can be grouped into three categories of:

When the resolution manager is about to perform a delete, insert, or update action, the corresponding event notification from the first set of events (deletingRow, insertingRow, and updatingRow) is generated. One of the parameters passed with the notification to these events is a ResolverResponse object. It is the responsibility of the event handler (also referred to as the event listener) to determine whether or not the action is appropriate and to return one of the following (ResolverResponse) responses:

If the event's response is resolve() (the default response), then one of the second set of events (deletedRow, insertedRow or updatedRow) is generated as appropriate. No response is expected from these events. They exist only to communicate to the application what action has been performed.

If the event's response is skip(), the current row is not resolved and the resolving process continues with the next row of data.

If the event terminates the resolution process with an abort(), no error event is generated, however, a generic ResolutionException is thrown to cancel the resolution process.

If an error occurs during the resolution processing, for example, the server did not allow a row to be deleted, then the appropriate error event (deleteError, insertError, or updateError) is generated. These events are passed the following:

It is the responsibility of the error event handler to:

If the event handler throws a DataSetException, it is treated as a ResolverResponse.abort(). In addition, it triggers the error event described above, passing along the user's Exception.

For an example of resolver events, see ResolverEvents.jpr and associated files in the samples\borland\samples\tutorial\dataset\ResolverEvents directory of your JBuilder installation. In the ResolverEvents application,

In the running application, you'll notice the following behavior: