Writing a custom data provider

Database application development is a feature of JBuilder Professional and Enterprise. Distributed application development is a feature of JBuilder Enterprise.

JBuilder makes it easy to write a custom provider 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: providers and resolvers. 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. JBuilder currently provides 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. These include:

You can create custom provider/resolver component implementations for EJB, application servers, SAP, BAAN, IMS, CICS, etc.

An example project with a custom provider and resolver is located in the /samples/DataExpress/CustomProviderResolver directory of your JBuilder installation. The sample file TestFrame.java is an application with a frame that contains a JdbTable and a JdbNavToolBar. Both visual components are connected to a TableDataSet component where data is provided from a custom Provider (defined in the file ProviderBean.java), and data is saved with a custom Resolver (defined in the file ResolverBean.java). This sample application reads from and saves changes to the text file data.txt, a simple non-delimited text file. The structure of data.txt is described in the interface file DataLayout.java.

This topic discusses custom data providers, and how they can be used as providers for a TableDataSet and any DataSet derived from TableDataSet. The main method to implement is provideData(com.borland.dx.dataset.StorageDataSet dataSet, boolean toOpen). This method accesses relevant metadata and loads the actual data into the data set.

Obtaining metadata

Metadata is information about the data. Examples of metadata are column name, table name, whether the column is part of the unique row id or not, whether it is searchable, its precision, scale, and so on. This information is typically obtained from the data source. The metadata is then stored in properties of Column components for each column in the StorageDataSet, and in the StorageDataSet itself.

When you obtain data from a data source, and store it in one of the subclasses of StorageDataSet, you typically obtain not only rows of data from the data source, but also metadata. For example, the first time that you ask a QueryDataSet to perform a query, by default it runs two queries: one for metadata discovery and the second for fetching rows of data that your application displays and manipulates. Subsequent queries performed by that instance of QueryDataSet only do row data fetching. After discovering the metadata, the QueryDataSet component then creates Column objects automatically as needed at run time. One Column is created for every query result column that is not already in the QueryDataSet. Each Column then gets some of its properties from the metadata, such as columnName, tableName, rowId, searchable, precision, scale, and so on.

When you are implementing the abstract provideData() method from the Provider class, the columns from the data provided may need to be added to your DataSet. This can be done by calling the ProviderHelp.initData() method from inside your provideData() implementation. Your provider should build an array of Columns to pass to the ProviderHelp.initData() method. The following is a list of Column properties that a Provider should consider initializing:

and optionally:

The optional properties are useful when saving changes back to a data source. The precision and scale properties are also used by DataSet components for constraint and display purposes.

Invoking initData

The arguments to the ProviderHelp.initData(com.borland.dx.dataset.StorageDataSet dataSet, com.borland.dx.dataset.Column[] columns, boolean updateColumns, boolean keepExistingColumns, boolean emptyRows) method are explained below.

If keepExistingColumns is true, non-persistent columns are also retained. Several column properties in the columns array are merged with existing columns in the StorageDataSet that have the same name property setting. If the number, type, and position of columns is different, this method may close the associated StorageDataSet.

The metaDataUpdate property on StorageDataSet is investigated when ProviderHelp.initData is called. This property controls which Column properties override properties in any persistent columns that are present in the TableDataSet before ProviderHelp.initData is called. Valid values for this property are defined in the MetaDataUpdate interface.

Obtaining actual data

Certain key DataSet methods cannot be used when the Provider.provideData method is called to open a DataSet, while the DataSet is in the process of being opened, including the StorageDataSet.insertRow() method.

In order to load the data, use the StorageDataSet.startLoading method. This method returns an array of Variant objects for all columns in a DataSet. You set the value in the array (the ordinal values of the columns are returned by the ProviderHelp.initData method), then load each row by calling the StorageDataSet.loadRow() method, and finish by calling the StorageDataSet.endLoading() method.

Tips on designing a custom data provider

A well designed provider recognizes the maxRows and maxDesignRows properties on StorageDataSet. The values for these properties are:

Value Description
0 provide metadata information only
-1 provide all data
n provide maximum of n rows

To determine if the provideData() method was called while in design mode, call java.beans.Beans.isDesignTime().

Understanding the provideData method in master-detail data sets

The Provider.provideData method is called

When a detail data set with the fetchAsNeeded property set to true needs to be loaded, the provider ignores provideData during the opening of the data, or just provides the metadata. The provider also uses the values of the masterLink fields to provide the rows for a specific detail data set.