You can create your own custom Provider, which can be used as a provider for a TableDataSet and any DataSet derived from TableDataSet. The main method to implement is: provideData(). This method is supposed to provide any metadata needed and loads the data into the storage of the dataset.
MetaData
Providing MetaData involves setting up the columns for the data. To do this an array of columns should be created with the wanted settings for:
and optionally:
These optional properties were the properties we found usefull when dealing with JDBC data sources. These properties are only used in JBuilder by classes in the borland.sql.dataset.* package.
When the Column array is created, the columns should be merged into the other persistent columns on the dataset by calling:
where
Note, that ProviderHelp.initData will close the dataset as a sideeffect. If initData is used outside the provideData method, the DataSet might have to be opened explicitly after this call.
Also note, that the MetaDataUpdate property on StorageDataSet is involved when ProviderHelp.initData is called. This property controls which Column properties will override properties in any persistent columns that are present in the TableDataSet before ProviderHelp.initData is called.
Actual Data
When provideData is called to open a dataSet, the dataSet is in the process of being opened i.e. it isn't done opening yet. Certain key dataSet methods cannot be used before a dataSet is successfully opened. This includes StorageDataSet.insertRow().
Thus the data itself should be loaded by using the StorageDataSet.startLoading() method. The ordinals of the proper columns is returned by ProviderHelp.initData. This is an array of ordinal numbers for each column in the column array of the MetaData passed to ProviderHelp.initData. Each row is then loaded by the StorageDataSet.loadRow() method and it should finish by calling StorageDataSet.endLoading().
Note: A well designed Provider will recognize the MaxRows and MaxDesignRows properties on StorageDataSet. A value of zero means: provide MetaData information only, and a value of -1 means: provide all data. Whether the provideData() was called while we are in design mode or not can be decided by calling: java.beans.Beans.isDesignTime().
When is provideData called
The Provider method "provideData" is called in 3 circumstances:
The third special case need some discussion. If a provider need to implement this master detail option, note that the provider should ignore the "provideData" during opening of the data. (Or just provide the metadata if possible). The provider will also have to use the values of the master link fields in order to provide the rows for a specific detail. Get the MasterLinkDescriptor from the StorageDataSet.
The "emptyRows" flag to ProviderHelp.initData will normally empty the entire rowset from the dataSet, however if "fetchAsNeeded" was specified in the MasterLinkDescriptor it will only empty the rows in the current detail. This is usefull for refresh().
You can create your own custom Resolver, which can be used as a Resolver for a TableDataSet. Usually a Resolver should resolve data back to the source, where a specific Provider got the data from initially. However there could be a choice of Providers for the same Resolver etc.
The main method to implement is: resolveData().
This method is supposed to
gather the changes of a StorageDataSet and resolve these back to the source.
First the StorageDataSet should be blocked for provider changes while the resolution process is going on. This is done by calling the methods:
All of the following logic should be placed in between the above method calls.
The changes may now be located by creating a DataSetView for each of the inserted, deleted, and updated rows. That is done by the following method calls:
It is important to note that:
It is also important to note that: each of the DataSetView's need to be closed after use (also if an exception occurs in the middle). The reason is, that the StorageDataSet will contain references to each DataSetView as long as that view is open. Such a view will thus never be garbage collected.
Resolver Errors
Errors can be handled in numerous ways, however the DataSet must be told to change the status of the changed rows. First each changed row should be marked RowStatus.PENDING_RESOLVED:
this method marks the current row as PENDING_RESOLVED. It should be called for each row, that is being resolved for each of the inserted, deleted, and updated rows.
Depending on the error handling approach one or more of the following method should be called hereafter:
All of these methods will reset the RowStatus.PENDING_RESOLVED bit.
The markPendingStatus will reset the current row. The first resetPendingStatus
will reset all the rows in the DataSet, and second resetPending will reset
the row with the specified internalRow id.
If the PENDING_RESOLVED bit is just reset the rows will still have status of
recorded changes.
If the rows are reset and "resolved" i.e. if one of the
resetPendingStatus methods are called with the parameter "resolved" set to true,
then:
Thus the row changes that were actually made to the data source should be reset with the "resolved" set to true. The row changes that never made it should just clear the PENDING_RESOLVED bit, such that the changes are still recorded in the DataSet.
Note, that some resolvers will choose to abandon all changes if there was just one error (that is the default behaviour of QueryDataSets.) Others may choose to commit certain changes, and keep the failed changes for error messages.
Master Detail Resolution
Master detail resolution presents yet another set of problems.
If the source of the data has referential integrity rules,
the dataSets may have to be resolved in a certain order. For JDBC JBuilder
solves this with the SQLResolutionManager class, which will make sure
to resolve master inserts before detail inserts, and resolve detail
deletions before master deletions.
DataSetData will eliminate most of the considerations mentioned in this paper. However DataSetData is only designed to move data from a DataSet into another DataSet. If however the source or destination of the data is not a DataSet it is recommended to implement custom providers and custom resolvers.
Use DataSetData.extractDataSet() and DataSetData.extractDataSetChanges() to extract data and necessary metadata from an existing dataSet, and use DataSetData.loadDataSet() to load it into e.g. an empty TableDataSet.